<!DOCTYPE html>
<html lang="zh-CN" dir="ltr">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>6.11 自定义 Sliver | 落光的Pro博客</title>
    <meta name="description" content="Vite & Vue powered static site generator.">
    <link rel="preload stylesheet" href="./assets/style.4b0df91d.css" as="style">
    
    <script type="module" src="./assets/app.0acbd3fd.js"></script>
    <link rel="preload" href="./assets/inter-roman-latin.2ed14f66.woff2" as="font" type="font/woff2" crossorigin="">
    <link rel="modulepreload" href="./assets/chunks/framework.eaf25f5b.js">
    <link rel="modulepreload" href="./assets/chunks/theme.ce44a0e6.js">
    <link rel="modulepreload" href="./assets/largeFrontEnd_flutter_chapter6_sliver.md.0c8b7553.lean.js">
    <script id="check-dark-light">(()=>{const e=localStorage.getItem("vitepress-theme-appearance")||"auto",a=window.matchMedia("(prefers-color-scheme: dark)").matches;(!e||e==="auto"?a:e==="dark")&&document.documentElement.classList.add("dark")})();</script>
  </head>
  <body>
    <div id="app"><div class="Layout" data-v-1919c326><!--[--><!--]--><!--[--><span tabindex="-1" data-v-0f60ec36></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-0f60ec36> Skip to content </a><!--]--><!----><header class="VPNav" data-v-1919c326 data-v-7e5bc4a5><div class="VPNavBar has-sidebar" data-v-7e5bc4a5 data-v-a0fd61f4><div class="container" data-v-a0fd61f4><div class="title" data-v-a0fd61f4><div class="VPNavBarTitle has-sidebar" data-v-a0fd61f4 data-v-86d1bed8><a class="title" href="./" data-v-86d1bed8><!--[--><!--]--><!--[--><img class="VPImage logo" src="./icon-radius.png" alt data-v-8426fc1a><!--]--><!--[-->落光的Pro博客<!--]--><!--[--><!--]--></a></div></div><div class="content" data-v-a0fd61f4><div class="curtain" data-v-a0fd61f4></div><div class="content-body" data-v-a0fd61f4><!--[--><!--]--><div class="VPNavBarSearch search" style="--vp-meta-key:&#39;Meta&#39;;" data-v-a0fd61f4><!--[--><!----><div id="local-search"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><span class="DocSearch-Button-Container"><svg class="DocSearch-Search-Icon" width="20" height="20" viewBox="0 0 20 20" aria-label="search icon"><path d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z" stroke="currentColor" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path></svg><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"><kbd class="DocSearch-Button-Key"></kbd><kbd class="DocSearch-Button-Key">K</kbd></span></button></div><!--]--></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-a0fd61f4 data-v-7f418b0f><span id="main-nav-aria-label" class="visually-hidden" data-v-7f418b0f>Main Navigation</span><!--[--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-9c007e85><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-9c007e85><span class="text" data-v-9c007e85><!----><span data-v-9c007e85>大前端</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-9c007e85><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-9c007e85><div class="VPMenu" data-v-9c007e85 data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuLink" data-v-e7ea1737 data-v-43f1e123><a class="VPLink link" href="./largeFrontEnd/webFrontEnd/md/01.html" data-v-43f1e123><!--[-->Web前端<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-e7ea1737 data-v-43f1e123><a class="VPLink link" href="./largeFrontEnd/uniapp/" data-v-43f1e123><!--[-->Uni-app<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-e7ea1737 data-v-43f1e123><a class="VPLink link" href="./largeFrontEnd/weChatMiniProgram/" data-v-43f1e123><!--[-->微信小程序<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-e7ea1737 data-v-43f1e123><a class="VPLink link" href="./largeFrontEnd/electron/" data-v-43f1e123><!--[-->Electron<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-e7ea1737 data-v-43f1e123><a class="VPLink link" href="./largeFrontEnd/flutter/" data-v-43f1e123><!--[-->Flutter<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-e7ea1737 data-v-43f1e123><a class="VPLink link" href="./largeFrontEnd/interview/01.html" data-v-43f1e123><!--[-->前端面试题<!--]--></a></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="./backEnd/" tabindex="0" data-v-7f418b0f data-v-42ef59de><!--[--><span data-v-42ef59de>后端</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="./linux/" tabindex="0" data-v-7f418b0f data-v-42ef59de><!--[--><span data-v-42ef59de>Linux</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="./myTeam/" tabindex="0" data-v-7f418b0f data-v-42ef59de><!--[--><span data-v-42ef59de>我的团队</span><!--]--></a><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-a0fd61f4 data-v-f6a63727><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title="toggle dark mode" aria-checked="false" data-v-f6a63727 data-v-ce54a7d1 data-v-b1685198><span class="check" data-v-b1685198><span class="icon" data-v-b1685198><!--[--><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="sun" data-v-ce54a7d1><path d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z"></path><path d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z"></path><path d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z"></path><path d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z"></path><path d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z"></path><path d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z"></path><path d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z"></path><path d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z"></path><path d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="moon" data-v-ce54a7d1><path d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"></path></svg><!--]--></span></span></button></div><div class="VPSocialLinks VPNavBarSocialLinks social-links" data-v-a0fd61f4 data-v-0394ad82 data-v-7bc22406><!--[--><a class="VPSocialLink no-icon" href="https://gitee.com/luoguangguang" aria-label="github" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></a><a class="VPSocialLink no-icon" href="..." aria-label="cool link" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Dribbble</title><path d="M12...6.38z"/></svg></a><!--]--></div><div class="VPFlyout VPNavBarExtra extra" data-v-a0fd61f4 data-v-40855f84 data-v-9c007e85><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-9c007e85><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="icon" data-v-9c007e85><circle cx="12" cy="12" r="2"></circle><circle cx="19" cy="12" r="2"></circle><circle cx="5" cy="12" r="2"></circle></svg></button><div class="menu" data-v-9c007e85><div class="VPMenu" data-v-9c007e85 data-v-e7ea1737><!----><!--[--><!--[--><!----><div class="group" data-v-40855f84><div class="item appearance" data-v-40855f84><p class="label" data-v-40855f84>Appearance</p><div class="appearance-action" data-v-40855f84><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title="toggle dark mode" aria-checked="false" data-v-40855f84 data-v-ce54a7d1 data-v-b1685198><span class="check" data-v-b1685198><span class="icon" data-v-b1685198><!--[--><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="sun" data-v-ce54a7d1><path d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z"></path><path d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z"></path><path d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z"></path><path d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z"></path><path d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z"></path><path d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z"></path><path d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z"></path><path d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z"></path><path d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="moon" data-v-ce54a7d1><path d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"></path></svg><!--]--></span></span></button></div></div></div><div class="group" data-v-40855f84><div class="item social-links" data-v-40855f84><div class="VPSocialLinks social-links-list" data-v-40855f84 data-v-7bc22406><!--[--><a class="VPSocialLink no-icon" href="https://gitee.com/luoguangguang" aria-label="github" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></a><a class="VPSocialLink no-icon" href="..." aria-label="cool link" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Dribbble</title><path d="M12...6.38z"/></svg></a><!--]--></div></div></div><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-a0fd61f4 data-v-e5dd9c1c><span class="container" data-v-e5dd9c1c><span class="top" data-v-e5dd9c1c></span><span class="middle" data-v-e5dd9c1c></span><span class="bottom" data-v-e5dd9c1c></span></span></button></div></div></div></div><!----></header><div class="VPLocalNav reached-top" data-v-1919c326 data-v-79c8c1df><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-79c8c1df><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="menu-icon" data-v-79c8c1df><path d="M17,11H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,11,17,11z"></path><path d="M21,7H3C2.4,7,2,6.6,2,6s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,7,21,7z"></path><path d="M21,15H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,15,21,15z"></path><path d="M17,19H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,19,17,19z"></path></svg><span class="menu-text" data-v-79c8c1df>Menu</span></button><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-79c8c1df data-v-1c15a60a><button data-v-1c15a60a>Return to top</button><!----></div></div><aside class="VPSidebar" data-v-1919c326 data-v-b00e2fdd><div class="curtain" data-v-b00e2fdd></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-b00e2fdd><span class="visually-hidden" id="sidebar-aria-label" data-v-b00e2fdd> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="group" data-v-b00e2fdd><section class="VPSidebarItem level-0 collapsible" data-v-b00e2fdd data-v-e31bd47b><div class="item" role="button" tabindex="0" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><h2 class="text" data-v-e31bd47b>Flutter入门到实战</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-e31bd47b><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-e31bd47b><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-e31bd47b><!--[--><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter1/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第一章 初识Flutter</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter2/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第二章 简介</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter3/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第三章 基础组件</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter4/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第四章 布局类组件</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter5/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第五章 容器类Widget</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter6/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第六章 可滚动组件</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter7/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第七章 功能型Widget简介</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter8/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第八章 事件处理与通知</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter9/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第九章 动画</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter10/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第十章 自定义组件</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter11/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第十一章 文件操作</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter12/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第十二章 Flutter 扩展</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter13/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第十三章 多语言</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-e31bd47b data-v-e31bd47b><div class="item" data-v-e31bd47b><div class="indicator" data-v-e31bd47b></div><a class="VPLink link link" href="./largeFrontEnd/flutter/chapter14/index.html" data-v-e31bd47b><!--[--><p class="text" data-v-e31bd47b>第十四章 高级进阶</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-1919c326 data-v-669faec9><div class="VPDoc has-sidebar has-aside" data-v-669faec9 data-v-6b87e69f><!--[--><!--]--><div class="container" data-v-6b87e69f><div class="aside" data-v-6b87e69f><div class="aside-curtain" data-v-6b87e69f></div><div class="aside-container" data-v-6b87e69f><div class="aside-content" data-v-6b87e69f><div class="VPDocAside" data-v-6b87e69f data-v-3f215769><!--[--><!--]--><!--[--><!--]--><div class="VPDocAsideOutline" role="navigation" data-v-3f215769 data-v-6ae8e080><div class="content" data-v-6ae8e080><div class="outline-marker" data-v-6ae8e080></div><div class="outline-title" role="heading" data-v-6ae8e080>On this page</div><nav aria-labelledby="doc-outline-aria-label" data-v-6ae8e080><span class="visually-hidden" id="doc-outline-aria-label" data-v-6ae8e080> Table of Contents for current page </span><ul class="root" data-v-6ae8e080 data-v-d0ee3533><!--[--><!--]--></ul></nav></div></div><!--[--><!--]--><div class="spacer" data-v-3f215769></div><!--[--><!--]--><div class="VPDocAsideCarbonAds" data-v-3f215769><div class="VPCarbonAds" data-v-2e1efd59></div></div><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-6b87e69f><div class="content-container" data-v-6b87e69f><!--[--><!--]--><!----><main class="main" data-v-6b87e69f><div style="position:relative;" class="vp-doc _largeFrontEnd_flutter_chapter6_sliver" data-v-6b87e69f><div><h1 id="_6-11-自定义-sliver" tabindex="-1">6.11 自定义 Sliver <a class="header-anchor" href="#_6-11-自定义-sliver" aria-label="Permalink to &quot;6.11 自定义 Sliver&quot;">​</a></h1><p>本节将通过自定义两个Sliver，来说明Sliver 布局协议和自定义 Sliver 的具体过程。</p><h2 id="_6-11-1-sliver-布局协议" tabindex="-1">6.11.1 Sliver 布局协议 <a class="header-anchor" href="#_6-11-1-sliver-布局协议" aria-label="Permalink to &quot;6.11.1 Sliver 布局协议&quot;">​</a></h2><p>Sliver 的布局协议如下：</p><ol><li>Viewport 将当前布局和配置信息通过 SliverConstraints 传递给 Sliver。</li><li>Sliver 确定自身的位置、绘制等信息，保存在 geometry 中（一个 SliverGeometry 类型的对象）。</li><li>Viewport 读取 geometry 中的信息来对 Sliver 进行布局和绘制。</li></ol><p>可以看到，这个过程有两个重要的对象 SliverConstraints 和 SliverGeometry ，我们先看看 SliverConstraints 的定义：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SliverConstraints</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Constraints</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//主轴方向</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">AxisDirection</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> axisDirection;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//Sliver 沿着主轴从列表的哪个方向插入？枚举类型，正向或反向</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">GrowthDirection</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> growthDirection;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//用户滑动方向</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">ScrollDirection</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> userScrollDirection;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//当前Sliver理论上（可能会固定在顶部）已经滑出可视区域的总偏移</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">double</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> scrollOffset;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//当前Sliver之前的Sliver占据的总高度，因为列表是懒加载，如果不能预估时，该值为double.infinity</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">double</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> precedingScrollExtent;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//上一个 sliver 覆盖当前 sliver 的长度（重叠部分的长度），通常在 sliver 是 pinned/floating</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//或者处于列表头尾时有效，我们在后面的小节中会有相关的例子。</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">double</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> overlap;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//当前Sliver在Viewport中的最大可以绘制的区域。</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//绘制如果超过该区域会比较低效（因为不会显示）</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">double</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> remainingPaintExtent;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//纵轴的长度；如果列表滚动方向是垂直方向，则表示列表宽度。</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">double</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> crossAxisExtent;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//纵轴方向</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">AxisDirection</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> crossAxisDirection;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//Viewport在主轴方向的长度；如果列表滚动方向是垂直方向，则表示列表高度。</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">double</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> viewportMainAxisExtent;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//Viewport 预渲染区域的起点[-Viewport.cacheExtent, 0]</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">double</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> cacheOrigin;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//Viewport加载区域的长度，范围:</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//[viewportMainAxisExtent,viewportMainAxisExtent + Viewport.cacheExtent*2]</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">double</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> remainingCacheExtent;</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SliverConstraints</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Constraints</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//主轴方向</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">AxisDirection</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> axisDirection;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//Sliver 沿着主轴从列表的哪个方向插入？枚举类型，正向或反向</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">GrowthDirection</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> growthDirection;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//用户滑动方向</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">ScrollDirection</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> userScrollDirection;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//当前Sliver理论上（可能会固定在顶部）已经滑出可视区域的总偏移</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">double</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> scrollOffset;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//当前Sliver之前的Sliver占据的总高度，因为列表是懒加载，如果不能预估时，该值为double.infinity</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">double</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> precedingScrollExtent;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//上一个 sliver 覆盖当前 sliver 的长度（重叠部分的长度），通常在 sliver 是 pinned/floating</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//或者处于列表头尾时有效，我们在后面的小节中会有相关的例子。</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">double</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> overlap;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//当前Sliver在Viewport中的最大可以绘制的区域。</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//绘制如果超过该区域会比较低效（因为不会显示）</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">double</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> remainingPaintExtent;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//纵轴的长度；如果列表滚动方向是垂直方向，则表示列表宽度。</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">double</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> crossAxisExtent;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//纵轴方向</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">AxisDirection</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> crossAxisDirection;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//Viewport在主轴方向的长度；如果列表滚动方向是垂直方向，则表示列表高度。</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">double</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> viewportMainAxisExtent;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//Viewport 预渲染区域的起点[-Viewport.cacheExtent, 0]</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">double</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> cacheOrigin;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//Viewport加载区域的长度，范围:</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//[viewportMainAxisExtent,viewportMainAxisExtent + Viewport.cacheExtent*2]</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">double</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> remainingCacheExtent;</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>可以看见 SliverConstraints 中包含的信息非常多。当列表滑动时，如果某个 Sliver 已经进入了需要构建的区域，则列表会将 SliverConstraints 信息传递给该 Sliver，Sliver 就可以根据这些信息来确定自身的布局和绘制信息了。</p><p>Sliver 需要确定的是 SliverGeometry：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">const</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SliverGeometry</span><span style="color:#E1E4E8;">({</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">//Sliver在主轴方向预估长度，大多数情况是固定值，用于计算sliverConstraints.scrollOffset</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.scrollExtent </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0.0</span><span style="color:#E1E4E8;">, </span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.paintExtent </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0.0</span><span style="color:#E1E4E8;">, </span><span style="color:#6A737D;">// 可视区域中的绘制长度</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.paintOrigin </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0.0</span><span style="color:#E1E4E8;">, </span><span style="color:#6A737D;">// 绘制的坐标原点，相对于自身布局位置</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">//在 Viewport中占用的长度；如果列表滚动方向是垂直方向，则表示列表高度。</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">//范围[0,paintExtent]</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">double</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> layoutExtent, </span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.maxPaintExtent </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0.0</span><span style="color:#E1E4E8;">,</span><span style="color:#6A737D;">//最大绘制长度</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.maxScrollObstructionExtent </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0.0</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">double</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> hitTestExtent, </span><span style="color:#6A737D;">// 点击测试的范围</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">bool</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> visible,</span><span style="color:#6A737D;">// 是否显示</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">//是否会溢出Viewport，如果为true，Viewport便会裁剪</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.hasVisualOverflow </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">false</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">//scrollExtent的修正值：layoutExtent变化后，为了防止sliver突然跳动（应用新的layoutExtent）</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">//可以先进行修正，具体的作用在后面 SliverFlexibleHeader 示例中会介绍。</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.scrollOffsetCorrection,</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">double</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> cacheExtent, </span><span style="color:#6A737D;">// 在预渲染区域中占据的长度</span></span>
<span class="line"><span style="color:#E1E4E8;">})</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">const</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SliverGeometry</span><span style="color:#24292E;">({</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">//Sliver在主轴方向预估长度，大多数情况是固定值，用于计算sliverConstraints.scrollOffset</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.scrollExtent </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0.0</span><span style="color:#24292E;">, </span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.paintExtent </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0.0</span><span style="color:#24292E;">, </span><span style="color:#6A737D;">// 可视区域中的绘制长度</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.paintOrigin </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0.0</span><span style="color:#24292E;">, </span><span style="color:#6A737D;">// 绘制的坐标原点，相对于自身布局位置</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">//在 Viewport中占用的长度；如果列表滚动方向是垂直方向，则表示列表高度。</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">//范围[0,paintExtent]</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">double</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> layoutExtent, </span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.maxPaintExtent </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0.0</span><span style="color:#24292E;">,</span><span style="color:#6A737D;">//最大绘制长度</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.maxScrollObstructionExtent </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0.0</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">double</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> hitTestExtent, </span><span style="color:#6A737D;">// 点击测试的范围</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">bool</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> visible,</span><span style="color:#6A737D;">// 是否显示</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">//是否会溢出Viewport，如果为true，Viewport便会裁剪</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.hasVisualOverflow </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">false</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">//scrollExtent的修正值：layoutExtent变化后，为了防止sliver突然跳动（应用新的layoutExtent）</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">//可以先进行修正，具体的作用在后面 SliverFlexibleHeader 示例中会介绍。</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.scrollOffsetCorrection,</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">double</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> cacheExtent, </span><span style="color:#6A737D;">// 在预渲染区域中占据的长度</span></span>
<span class="line"><span style="color:#24292E;">})</span></span></code></pre></div><h3 id="sliver布局模型和盒布局模型" tabindex="-1">Sliver布局模型和盒布局模型 <a class="header-anchor" href="#sliver布局模型和盒布局模型" aria-label="Permalink to &quot;Sliver布局模型和盒布局模型&quot;">​</a></h3><p>两者布局流程基本相同：父组件告诉子组件约束信息 &gt; 子组件根据父组件的约束确定自生大小 &gt; 父组件获得子组件大小调整其位置。不同是：</p><ol><li>父组件传递给子组件的约束信息不同。盒模型传递的是 BoxConstraints，而 Sliver 传递的是 SliverConstraints。</li><li>描述子组件布局信息的对象不同。盒模型的布局信息通过 Size 和 offset描述 ，而 Sliver的是通过 SliverGeometry 描述。</li><li>布局的起点不同。Sliver布局的起点一般是Viewport ，而盒模型布局的起点可以是任意的组件。</li></ol><p>SliverConstraints 和 SliverGeometry 属性比较多，只看的话它们的含义并不好理解，下面我们将通过两个例子，通过实践来理解。</p><h2 id="_6-11-2-自定义-sliver-一-sliverflexibleheader" tabindex="-1">6.11.2 自定义 Sliver（一）SliverFlexibleHeader <a class="header-anchor" href="#_6-11-2-自定义-sliver-一-sliverflexibleheader" aria-label="Permalink to &quot;6.11.2 自定义 Sliver（一）SliverFlexibleHeader&quot;">​</a></h2><h3 id="_1-sliverflexibleheader" tabindex="-1">1. SliverFlexibleHeader <a class="header-anchor" href="#_1-sliverflexibleheader" aria-label="Permalink to &quot;1. SliverFlexibleHeader&quot;">​</a></h3><p>我们实现一个类似旧版本微信朋友圈顶部头图的功能：即默认情况下顶部图片只显示一部分，当用户向下拽时图片的剩余部分会逐渐显示，如图6-28所示。</p><p><img src="/assets/6-28.a2dd3921.gif" alt="6-28"></p><p>我们的思路是实现一个 Sliver，将它作为 CustomScrollView 的第一孩子，然后根据用户的滑动来动态调整 Sliver 的布局和显示。下面我们来实现一个 SliverFlexibleHeader，它会结合 CustomScrollView 实现上述效果。我们先看一下页面的整体实现代码：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">build</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context) {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">CustomScrollView</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//为了能使CustomScrollView拉到顶部时还能继续往下拉，必须让 physics 支持弹性效果</span></span>
<span class="line"><span style="color:#E1E4E8;">    physics</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">const</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">BouncingScrollPhysics</span><span style="color:#E1E4E8;">(parent</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">AlwaysScrollableScrollPhysics</span><span style="color:#E1E4E8;">()),</span></span>
<span class="line"><span style="color:#E1E4E8;">    slivers</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> [</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#6A737D;">//我们需要实现的 SliverFlexibleHeader 组件</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#79B8FF;">SliverFlexibleHeader</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">        visibleExtent</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">200</span><span style="color:#E1E4E8;">,, </span><span style="color:#6A737D;">// 初始状态在列表中占用的布局高度</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#6A737D;">// 为了能根据下拉状态变化来定制显示的布局，我们通过一个 builder 来动态构建布局。</span></span>
<span class="line"><span style="color:#E1E4E8;">        builder</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> (context, availableHeight, direction) {</span></span>
<span class="line"><span style="color:#E1E4E8;">          </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">GestureDetector</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">            onTap</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> () </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">print</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">&#39;tap&#39;</span><span style="color:#E1E4E8;">), </span><span style="color:#6A737D;">//测试是否可以响应事件</span></span>
<span class="line"><span style="color:#E1E4E8;">            child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Image</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">              image</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">AssetImage</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">&quot;imgs/avatar.png&quot;</span><span style="color:#E1E4E8;">),</span></span>
<span class="line"><span style="color:#E1E4E8;">              width</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">50.0</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">              height</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> availableHeight,</span></span>
<span class="line"><span style="color:#E1E4E8;">              alignment</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Alignment</span><span style="color:#E1E4E8;">.bottomCenter,</span></span>
<span class="line"><span style="color:#E1E4E8;">              fit</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">BoxFit</span><span style="color:#E1E4E8;">.cover,</span></span>
<span class="line"><span style="color:#E1E4E8;">            ),</span></span>
<span class="line"><span style="color:#E1E4E8;">          );</span></span>
<span class="line"><span style="color:#E1E4E8;">        },</span></span>
<span class="line"><span style="color:#E1E4E8;">      ),</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#6A737D;">// 构建一个list</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#B392F0;">buildSliverList</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">30</span><span style="color:#E1E4E8;">),</span></span>
<span class="line"><span style="color:#E1E4E8;">    ],</span></span>
<span class="line"><span style="color:#E1E4E8;">  );</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">build</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context) {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">CustomScrollView</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//为了能使CustomScrollView拉到顶部时还能继续往下拉，必须让 physics 支持弹性效果</span></span>
<span class="line"><span style="color:#24292E;">    physics</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">const</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">BouncingScrollPhysics</span><span style="color:#24292E;">(parent</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">AlwaysScrollableScrollPhysics</span><span style="color:#24292E;">()),</span></span>
<span class="line"><span style="color:#24292E;">    slivers</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> [</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6A737D;">//我们需要实现的 SliverFlexibleHeader 组件</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#005CC5;">SliverFlexibleHeader</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">        visibleExtent</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">200</span><span style="color:#24292E;">,, </span><span style="color:#6A737D;">// 初始状态在列表中占用的布局高度</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#6A737D;">// 为了能根据下拉状态变化来定制显示的布局，我们通过一个 builder 来动态构建布局。</span></span>
<span class="line"><span style="color:#24292E;">        builder</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> (context, availableHeight, direction) {</span></span>
<span class="line"><span style="color:#24292E;">          </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">GestureDetector</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">            onTap</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> () </span><span style="color:#D73A49;">=&gt;</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">print</span><span style="color:#24292E;">(</span><span style="color:#032F62;">&#39;tap&#39;</span><span style="color:#24292E;">), </span><span style="color:#6A737D;">//测试是否可以响应事件</span></span>
<span class="line"><span style="color:#24292E;">            child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Image</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">              image</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">AssetImage</span><span style="color:#24292E;">(</span><span style="color:#032F62;">&quot;imgs/avatar.png&quot;</span><span style="color:#24292E;">),</span></span>
<span class="line"><span style="color:#24292E;">              width</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">50.0</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">              height</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> availableHeight,</span></span>
<span class="line"><span style="color:#24292E;">              alignment</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Alignment</span><span style="color:#24292E;">.bottomCenter,</span></span>
<span class="line"><span style="color:#24292E;">              fit</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">BoxFit</span><span style="color:#24292E;">.cover,</span></span>
<span class="line"><span style="color:#24292E;">            ),</span></span>
<span class="line"><span style="color:#24292E;">          );</span></span>
<span class="line"><span style="color:#24292E;">        },</span></span>
<span class="line"><span style="color:#24292E;">      ),</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6A737D;">// 构建一个list</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6F42C1;">buildSliverList</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">30</span><span style="color:#24292E;">),</span></span>
<span class="line"><span style="color:#24292E;">    ],</span></span>
<span class="line"><span style="color:#24292E;">  );</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>接下来，我们的重点是实现 SliverFlexibleHeader，由于涉及到 Sliver 布局，通过现有组件很难组合实现我们想要的功能，所以我们通过定制 RenderObject 的方式来实现它。为了能根据下拉位置的变化来动态调整，SliverFlexibleHeader 中我们通过一个 builder 来动态构建布局，当下拉位置发生变化时，builder 就会被回调。</p><p>为了清晰起见，我们先实现一个接收固定 widget 的 _SliverFlexibleHeader 组件，组件定义代码如下：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">_SliverFlexibleHeader</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SingleChildRenderObjectWidget</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">const</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">_SliverFlexibleHeader</span><span style="color:#E1E4E8;">({</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">Key</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> key,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">required</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> child,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.visibleExtent </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">  }) </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">super</span><span style="color:#E1E4E8;">(key</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> key, child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> child);</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> visibleExtent;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">RenderObject</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">createRenderObject</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context) {</span></span>
<span class="line"><span style="color:#E1E4E8;">   </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">_FlexibleHeaderRenderSliver</span><span style="color:#E1E4E8;">(visibleExtent);</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">updateRenderObject</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context, </span><span style="color:#79B8FF;">_FlexibleHeaderRenderSliver</span><span style="color:#E1E4E8;"> renderObject) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    renderObject..visibleExtent </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> visibleExtent;</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">_SliverFlexibleHeader</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SingleChildRenderObjectWidget</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">const</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">_SliverFlexibleHeader</span><span style="color:#24292E;">({</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">Key</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> key,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">required</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> child,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.visibleExtent </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">  }) </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">super</span><span style="color:#24292E;">(key</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> key, child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> child);</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> visibleExtent;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">RenderObject</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">createRenderObject</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context) {</span></span>
<span class="line"><span style="color:#24292E;">   </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">_FlexibleHeaderRenderSliver</span><span style="color:#24292E;">(visibleExtent);</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">updateRenderObject</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context, </span><span style="color:#005CC5;">_FlexibleHeaderRenderSliver</span><span style="color:#24292E;"> renderObject) {</span></span>
<span class="line"><span style="color:#24292E;">    renderObject..visibleExtent </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> visibleExtent;</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>这里我们继承的既不是 StatelessWidget，也不是 StatefulWidget，这是因为这两个组件主要的作用是组合 Widget，而我们要自定义 RenderObject，则需要继承 RenderObjectWidget，考虑到_SliverFlexibleHeader 有一个子节点，我们可以直接继承 SingleChildRenderObjectWidget 类，这样我们可以省去一些和布局无关的代码，比如绘制和事件的点击测试，这些功能 SingleChildRenderObjectWidget 中已经帮我们处理了。</p><p>下面我们实现 _FlexibleHeaderRenderSliver，核心代码就在 performLayout 中，读者可参考注释：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">_FlexibleHeaderRenderSliver</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">RenderSliverSingleBoxAdapter</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">_FlexibleHeaderRenderSliver</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> visibleExtent)</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> _visibleExtent </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> visibleExtent;</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> _lastOverScroll </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> _lastScrollOffset </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">late</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> _visibleExtent </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">set</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">visibleExtent</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> value) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 可视长度发生变化，更新状态并重新布局</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (_visibleExtent </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> value) {</span></span>
<span class="line"><span style="color:#E1E4E8;">      _lastOverScroll </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">      _visibleExtent </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> value;</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#B392F0;">markNeedsLayout</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">    }</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">performLayout</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 滑动距离大于_visibleExtent时则表示子节点已经在屏幕之外了</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (child </span><span style="color:#F97583;">==</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">null</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">||</span><span style="color:#E1E4E8;"> (constraints.scrollOffset </span><span style="color:#F97583;">&gt;</span><span style="color:#E1E4E8;"> _visibleExtent)) {</span></span>
<span class="line"><span style="color:#E1E4E8;">      geometry </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SliverGeometry</span><span style="color:#E1E4E8;">(scrollExtent</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> _visibleExtent);</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 测试overlap,下拉过程中overlap会一直变化.</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> overScroll </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> constraints.overlap </span><span style="color:#F97583;">&lt;</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> constraints.overlap.</span><span style="color:#B392F0;">abs</span><span style="color:#E1E4E8;">() </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">var</span><span style="color:#E1E4E8;"> scrollOffset </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> constraints.scrollOffset;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 在Viewport中顶部的可视空间为该 Sliver 可绘制的最大区域。</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 1. 如果Sliver已经滑出可视区域则 constraints.scrollOffset 会大于 _visibleExtent，</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//    这种情况我们在一开始就判断过了。</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 2. 如果我们下拉超出了边界，此时 overScroll&gt;0，scrollOffset 值为0，所以最终的绘制区域为</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//    _visibleExtent + overScroll.</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> paintExtent </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> _visibleExtent </span><span style="color:#F97583;">+</span><span style="color:#E1E4E8;"> overScroll </span><span style="color:#F97583;">-</span><span style="color:#E1E4E8;"> constraints.scrollOffset;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 绘制高度不超过最大可绘制空间</span></span>
<span class="line"><span style="color:#E1E4E8;">    paintExtent </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">min</span><span style="color:#E1E4E8;">(paintExtent, constraints.remainingPaintExtent);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//对子组件进行布局，关于 layout 详细过程我们将在本书后面布局原理相关章节详细介绍，现在只需知道</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//子组件通过 LayoutBuilder可以拿到这里我们传递的约束对象（ExtraInfoBoxConstraints）</span></span>
<span class="line"><span style="color:#E1E4E8;">    child</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">layout</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      constraints.</span><span style="color:#B392F0;">asBoxConstraints</span><span style="color:#E1E4E8;">(maxExtent</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> paintExtent),</span></span>
<span class="line"><span style="color:#E1E4E8;">      parentUsesSize</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">false</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//最大为_visibleExtent，最小为 0</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> layoutExtent </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">min</span><span style="color:#E1E4E8;">(_visibleExtent, paintExtent);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//设置geometry，Viewport 在布局时会用到</span></span>
<span class="line"><span style="color:#E1E4E8;">    geometry </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SliverGeometry</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      scrollExtent</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> layoutExtent,</span></span>
<span class="line"><span style="color:#E1E4E8;">      paintOrigin</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">-</span><span style="color:#E1E4E8;">overScroll,</span></span>
<span class="line"><span style="color:#E1E4E8;">      paintExtent</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> paintExtent,</span></span>
<span class="line"><span style="color:#E1E4E8;">      maxPaintExtent</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> paintExtent,</span></span>
<span class="line"><span style="color:#E1E4E8;">      layoutExtent</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> layoutExtent,</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">_FlexibleHeaderRenderSliver</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">RenderSliverSingleBoxAdapter</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">_FlexibleHeaderRenderSliver</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> visibleExtent)</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> _visibleExtent </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> visibleExtent;</span></span>
<span class="line"><span style="color:#24292E;">  </span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> _lastOverScroll </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> _lastScrollOffset </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">late</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> _visibleExtent </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">set</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">visibleExtent</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> value) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 可视长度发生变化，更新状态并重新布局</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (_visibleExtent </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> value) {</span></span>
<span class="line"><span style="color:#24292E;">      _lastOverScroll </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">      _visibleExtent </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> value;</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6F42C1;">markNeedsLayout</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">    }</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">performLayout</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 滑动距离大于_visibleExtent时则表示子节点已经在屏幕之外了</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (child </span><span style="color:#D73A49;">==</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">null</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">||</span><span style="color:#24292E;"> (constraints.scrollOffset </span><span style="color:#D73A49;">&gt;</span><span style="color:#24292E;"> _visibleExtent)) {</span></span>
<span class="line"><span style="color:#24292E;">      geometry </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SliverGeometry</span><span style="color:#24292E;">(scrollExtent</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> _visibleExtent);</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#D73A49;">return</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 测试overlap,下拉过程中overlap会一直变化.</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> overScroll </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> constraints.overlap </span><span style="color:#D73A49;">&lt;</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> constraints.overlap.</span><span style="color:#6F42C1;">abs</span><span style="color:#24292E;">() </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">var</span><span style="color:#24292E;"> scrollOffset </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> constraints.scrollOffset;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 在Viewport中顶部的可视空间为该 Sliver 可绘制的最大区域。</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 1. 如果Sliver已经滑出可视区域则 constraints.scrollOffset 会大于 _visibleExtent，</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//    这种情况我们在一开始就判断过了。</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 2. 如果我们下拉超出了边界，此时 overScroll&gt;0，scrollOffset 值为0，所以最终的绘制区域为</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//    _visibleExtent + overScroll.</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> paintExtent </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> _visibleExtent </span><span style="color:#D73A49;">+</span><span style="color:#24292E;"> overScroll </span><span style="color:#D73A49;">-</span><span style="color:#24292E;"> constraints.scrollOffset;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 绘制高度不超过最大可绘制空间</span></span>
<span class="line"><span style="color:#24292E;">    paintExtent </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">min</span><span style="color:#24292E;">(paintExtent, constraints.remainingPaintExtent);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//对子组件进行布局，关于 layout 详细过程我们将在本书后面布局原理相关章节详细介绍，现在只需知道</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//子组件通过 LayoutBuilder可以拿到这里我们传递的约束对象（ExtraInfoBoxConstraints）</span></span>
<span class="line"><span style="color:#24292E;">    child</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">layout</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      constraints.</span><span style="color:#6F42C1;">asBoxConstraints</span><span style="color:#24292E;">(maxExtent</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> paintExtent),</span></span>
<span class="line"><span style="color:#24292E;">      parentUsesSize</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">false</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//最大为_visibleExtent，最小为 0</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> layoutExtent </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">min</span><span style="color:#24292E;">(_visibleExtent, paintExtent);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//设置geometry，Viewport 在布局时会用到</span></span>
<span class="line"><span style="color:#24292E;">    geometry </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SliverGeometry</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      scrollExtent</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> layoutExtent,</span></span>
<span class="line"><span style="color:#24292E;">      paintOrigin</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">-</span><span style="color:#24292E;">overScroll,</span></span>
<span class="line"><span style="color:#24292E;">      paintExtent</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> paintExtent,</span></span>
<span class="line"><span style="color:#24292E;">      maxPaintExtent</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> paintExtent,</span></span>
<span class="line"><span style="color:#24292E;">      layoutExtent</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> layoutExtent,</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>在 performLayout 中我们通过 Viewport 传来的 SliverConstraints 结合子组件的高度，最终确定了 <code>_SliverFlexibleHeader</code> 的布局、绘制等相关信息，它们被保存在了 <code>geometry</code> 中，之后，Viewport 就可以读取 geometry 来确定 <code>_SliverFlexibleHeader</code> 在 Viewport 中的位置，然后进行绘制。读者可以手动修改一下 SliverGeometry 的各个属性，看看效果，这样可以加深理解。</p><p>现在还剩最后一个问题，<code>_SliverFlexibleHeader</code> 接收的是一个固定的 widget，我们如何在下拉位置发生变化时来重新构建 widget 呢？上面代码中，我们在 <code>_SliverFlexibleHeader</code> 的 performLayout 方法中，每当下拉位置发生变化，我们都会对其子组件重新进行 layout。那既然如此，我们可以创建一个 LayoutBuilder 用于在子组件重新布局时来动态构建 child。思路有了，那么实现很简单，我们看看最终的 SliverFlexibleHeader 实现：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">typedef</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SliverFlexibleHeaderBuilder</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Function</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context,</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> maxExtent,</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">//ScrollDirection direction,</span></span>
<span class="line"><span style="color:#E1E4E8;">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SliverFlexibleHeader</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">StatelessWidget</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">const</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SliverFlexibleHeader</span><span style="color:#E1E4E8;">({</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">Key</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> key,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.visibleExtent </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">required</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.builder,</span></span>
<span class="line"><span style="color:#E1E4E8;">  }) </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">super</span><span style="color:#E1E4E8;">(key</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> key);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SliverFlexibleHeaderBuilder</span><span style="color:#E1E4E8;"> builder;</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> visibleExtent;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">build</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">_SliverFlexibleHeader</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      visibleExtent</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> visibleExtent,</span></span>
<span class="line"><span style="color:#E1E4E8;">      child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">LayoutBuilder</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">        builder</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> (</span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context, </span><span style="color:#79B8FF;">BoxConstraints</span><span style="color:#E1E4E8;"> constraints) {</span></span>
<span class="line"><span style="color:#E1E4E8;">          </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">builder</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">            context,</span></span>
<span class="line"><span style="color:#E1E4E8;">            constraints.maxHeight</span></span>
<span class="line"><span style="color:#E1E4E8;">          );</span></span>
<span class="line"><span style="color:#E1E4E8;">        },</span></span>
<span class="line"><span style="color:#E1E4E8;">      ),</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">typedef</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SliverFlexibleHeaderBuilder</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Function</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context,</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> maxExtent,</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">//ScrollDirection direction,</span></span>
<span class="line"><span style="color:#24292E;">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SliverFlexibleHeader</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">StatelessWidget</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">const</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SliverFlexibleHeader</span><span style="color:#24292E;">({</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">Key</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> key,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.visibleExtent </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">required</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.builder,</span></span>
<span class="line"><span style="color:#24292E;">  }) </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">super</span><span style="color:#24292E;">(key</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> key);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SliverFlexibleHeaderBuilder</span><span style="color:#24292E;"> builder;</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> visibleExtent;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">build</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">_SliverFlexibleHeader</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      visibleExtent</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> visibleExtent,</span></span>
<span class="line"><span style="color:#24292E;">      child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">LayoutBuilder</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">        builder</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> (</span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context, </span><span style="color:#005CC5;">BoxConstraints</span><span style="color:#24292E;"> constraints) {</span></span>
<span class="line"><span style="color:#24292E;">          </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">builder</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">            context,</span></span>
<span class="line"><span style="color:#24292E;">            constraints.maxHeight</span></span>
<span class="line"><span style="color:#24292E;">          );</span></span>
<span class="line"><span style="color:#24292E;">        },</span></span>
<span class="line"><span style="color:#24292E;">      ),</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>当<code> _SliverFlexibleHeader</code> 中每次对子组件进行布局时，都会触发 LayoutBuilder 来重新构建子 widget ，LayoutBuilder 中收到的 constraints 就是 <code> _SliverFlexibleHeader</code> 中对子组件进行布局时 传入的 constraints，即：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#E1E4E8;">...</span></span>
<span class="line"><span style="color:#E1E4E8;">child</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">layout</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">//对子组件进行布局</span></span>
<span class="line"><span style="color:#E1E4E8;">  constraints.</span><span style="color:#B392F0;">asBoxConstraints</span><span style="color:#E1E4E8;">(maxExtent</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> paintExtent),</span></span>
<span class="line"><span style="color:#E1E4E8;">  parentUsesSize</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">true</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">);</span></span>
<span class="line"><span style="color:#E1E4E8;">...</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#24292E;">...</span></span>
<span class="line"><span style="color:#24292E;">child</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">layout</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">//对子组件进行布局</span></span>
<span class="line"><span style="color:#24292E;">  constraints.</span><span style="color:#6F42C1;">asBoxConstraints</span><span style="color:#24292E;">(maxExtent</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> paintExtent),</span></span>
<span class="line"><span style="color:#24292E;">  parentUsesSize</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">true</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">);</span></span>
<span class="line"><span style="color:#24292E;">...</span></span></code></pre></div><h3 id="_2-传递额外的布局信息" tabindex="-1">2. 传递额外的布局信息 <a class="header-anchor" href="#_2-传递额外的布局信息" aria-label="Permalink to &quot;2. 传递额外的布局信息&quot;">​</a></h3><p>在实际使用 SliverFlexibleHeader 时，我们有时在构建子 widget 时可能会依赖当前列表的滑动方向，当然我们可以在 SliverFlexibleHeader 的 builder 中记录前后的 availableHeight 的差来确定滑动方向，但是这样比较麻烦，需要使用者来手动处理。我们知道在滑动时，Sliver 的 SliverConstraints 中已经包含了 <code>userScrollDirection</code>，如果我们能将它经过统一的处理然后透传给 LayoutBuilder 的话就非常好好了，这样就不需要开发者在使用时自己维护滑动方向了！按照这个思路我们来实现一下。</p><p>首先我们遇到了第一个问题： LayoutBuilder 接收的参数我们没法指定。为此笔者想到了两种方案：</p><ol><li>我们知道在上面的场景中，在对子组件进行布局时我们传给子组件的约束只使用了最大长度，最小长度是没有用到的，那么我们可以将滑动方向通过最小长度传递给 LayoutBuilder，然后再 LayoutBuilder 中取出即可。</li><li>定义一个新类，让它继承自 <code>BoxConstraints</code>，然后再添加一个可以保存 scrollDirection 的属性。</li></ol><p>笔者试了一下，两种方案都能成功，那应该使用哪种方案呢？笔者建议使用方案 2 ，因为方案 1 有一个副作用就是会影响子组件布局。我们知道 LayoutBuilder 是在子组件 build 阶段执行的，当我们设置了最小长度后，我们虽然在 build 阶段没有用到它，但是在子组件在布局阶段仍然会应用此约束，所以最终还会影响子组件的布局。</p><p>下面我们按照方案 2 来实现：定义一个 ExtraInfoBoxConstraints 类，它可以携带约束之外的信息，为了尽可能通用，我们使用泛型：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">ExtraInfoBoxConstraints</span><span style="color:#E1E4E8;">&lt;</span><span style="color:#79B8FF;">T</span><span style="color:#E1E4E8;">&gt; </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">BoxConstraints</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">ExtraInfoBoxConstraints</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.extra,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">BoxConstraints</span><span style="color:#E1E4E8;"> constraints,</span></span>
<span class="line"><span style="color:#E1E4E8;">  ) </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">super</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">          minWidth</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> constraints.minWidth,</span></span>
<span class="line"><span style="color:#E1E4E8;">          minHeight</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> constraints.minHeight,</span></span>
<span class="line"><span style="color:#E1E4E8;">          maxWidth</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> constraints.maxWidth,</span></span>
<span class="line"><span style="color:#E1E4E8;">          maxHeight</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> constraints.maxHeight,</span></span>
<span class="line"><span style="color:#E1E4E8;">        );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// 额外的信息</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">T</span><span style="color:#E1E4E8;"> extra;</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">bool</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">operator</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">==</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">Object</span><span style="color:#E1E4E8;"> other) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (</span><span style="color:#B392F0;">identical</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">, other)) </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">true</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> other </span><span style="color:#F97583;">is</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">ExtraInfoBoxConstraints</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">&amp;&amp;</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#79B8FF;">super</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">==</span><span style="color:#E1E4E8;"> other </span><span style="color:#F97583;">&amp;&amp;</span></span>
<span class="line"><span style="color:#E1E4E8;">        other.extra </span><span style="color:#F97583;">==</span><span style="color:#E1E4E8;"> extra;</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">int</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">get</span><span style="color:#E1E4E8;"> hashCode {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">hashValues</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">super</span><span style="color:#E1E4E8;">.hashCode, extra);</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">ExtraInfoBoxConstraints</span><span style="color:#24292E;">&lt;</span><span style="color:#005CC5;">T</span><span style="color:#24292E;">&gt; </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">BoxConstraints</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">ExtraInfoBoxConstraints</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.extra,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">BoxConstraints</span><span style="color:#24292E;"> constraints,</span></span>
<span class="line"><span style="color:#24292E;">  ) </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">super</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">          minWidth</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> constraints.minWidth,</span></span>
<span class="line"><span style="color:#24292E;">          minHeight</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> constraints.minHeight,</span></span>
<span class="line"><span style="color:#24292E;">          maxWidth</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> constraints.maxWidth,</span></span>
<span class="line"><span style="color:#24292E;">          maxHeight</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> constraints.maxHeight,</span></span>
<span class="line"><span style="color:#24292E;">        );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// 额外的信息</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">T</span><span style="color:#24292E;"> extra;</span></span>
<span class="line"><span style="color:#24292E;">  </span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">bool</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">operator</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">==</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">Object</span><span style="color:#24292E;"> other) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (</span><span style="color:#6F42C1;">identical</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">this</span><span style="color:#24292E;">, other)) </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">true</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> other </span><span style="color:#D73A49;">is</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">ExtraInfoBoxConstraints</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">&amp;&amp;</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#005CC5;">super</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">==</span><span style="color:#24292E;"> other </span><span style="color:#D73A49;">&amp;&amp;</span></span>
<span class="line"><span style="color:#24292E;">        other.extra </span><span style="color:#D73A49;">==</span><span style="color:#24292E;"> extra;</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">int</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">get</span><span style="color:#24292E;"> hashCode {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">hashValues</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">super</span><span style="color:#24292E;">.hashCode, extra);</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>上面代码比较简单，要说明的是我们重载了“==”运算符，这是因为 Flutter 在布局期间在特定的情况下会检测前后两次 constraints 是否相等然后来决定是否需要重新布局，所以我们需要重载“==”运算符，否则可能会在最大/最小宽高不变但 extra 发生变化时不会触发 child 重新布局，这时也就不会触发 LayoutBuilder，这明显不符合预期，因为我们希望 extra 发生变化时，会触发 LayoutBuilder 重新构建 child。</p><p>首先我们修改 <code>__FlexibleHeaderRenderSliver</code> 的 performLayout 方法：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#E1E4E8;">...</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">//对子组件进行布局，子组件通过 LayoutBuilder可以拿到这里我们传递的约束对象（ExtraInfoBoxConstraints）</span></span>
<span class="line"><span style="color:#E1E4E8;">  child</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">layout</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">ExtraInfoBoxConstraints</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">    direction, </span><span style="color:#6A737D;">//传递滑动方向</span></span>
<span class="line"><span style="color:#E1E4E8;">    constraints.</span><span style="color:#B392F0;">asBoxConstraints</span><span style="color:#E1E4E8;">(maxExtent</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> paintExtent),</span></span>
<span class="line"><span style="color:#E1E4E8;">  ),</span></span>
<span class="line"><span style="color:#E1E4E8;">  parentUsesSize</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">false</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">);</span></span>
<span class="line"><span style="color:#E1E4E8;">...</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#24292E;">...</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">//对子组件进行布局，子组件通过 LayoutBuilder可以拿到这里我们传递的约束对象（ExtraInfoBoxConstraints）</span></span>
<span class="line"><span style="color:#24292E;">  child</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">layout</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">ExtraInfoBoxConstraints</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">    direction, </span><span style="color:#6A737D;">//传递滑动方向</span></span>
<span class="line"><span style="color:#24292E;">    constraints.</span><span style="color:#6F42C1;">asBoxConstraints</span><span style="color:#24292E;">(maxExtent</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> paintExtent),</span></span>
<span class="line"><span style="color:#24292E;">  ),</span></span>
<span class="line"><span style="color:#24292E;">  parentUsesSize</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">false</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">);</span></span>
<span class="line"><span style="color:#24292E;">...</span></span></code></pre></div><p>然后修改 SliverFlexibleHeader 实现，在 LayoutBuilder 中就可以获取到滑动方向：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">typedef</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SliverFlexibleHeaderBuilder</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Function</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context,</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> maxExtent,</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">ScrollDirection</span><span style="color:#E1E4E8;"> direction,</span></span>
<span class="line"><span style="color:#E1E4E8;">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SliverFlexibleHeader</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">StatelessWidget</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">const</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SliverFlexibleHeader</span><span style="color:#E1E4E8;">({</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">Key</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> key,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.visibleExtent </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">required</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.builder,</span></span>
<span class="line"><span style="color:#E1E4E8;">  }) </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">super</span><span style="color:#E1E4E8;">(key</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> key);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SliverFlexibleHeaderBuilder</span><span style="color:#E1E4E8;"> builder;</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> visibleExtent;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">build</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">_SliverFlexibleHeader</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      visibleExtent</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> visibleExtent,</span></span>
<span class="line"><span style="color:#E1E4E8;">      child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">LayoutBuilder</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">        builder</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> (</span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context, </span><span style="color:#79B8FF;">BoxConstraints</span><span style="color:#E1E4E8;"> constraints) {</span></span>
<span class="line"><span style="color:#E1E4E8;">          </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">builder</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">            context,</span></span>
<span class="line"><span style="color:#E1E4E8;">            constraints.maxHeight,</span></span>
<span class="line"><span style="color:#E1E4E8;">            </span><span style="color:#6A737D;">// 获取滑动方向</span></span>
<span class="line"><span style="color:#E1E4E8;">            (constraints </span><span style="color:#F97583;">as</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">ExtraInfoBoxConstraints</span><span style="color:#E1E4E8;">&lt;</span><span style="color:#79B8FF;">ScrollDirection</span><span style="color:#E1E4E8;">&gt;).extra,</span></span>
<span class="line"><span style="color:#E1E4E8;">          );</span></span>
<span class="line"><span style="color:#E1E4E8;">        },</span></span>
<span class="line"><span style="color:#E1E4E8;">      ),</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">typedef</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SliverFlexibleHeaderBuilder</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Function</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context,</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> maxExtent,</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">ScrollDirection</span><span style="color:#24292E;"> direction,</span></span>
<span class="line"><span style="color:#24292E;">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SliverFlexibleHeader</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">StatelessWidget</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">const</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SliverFlexibleHeader</span><span style="color:#24292E;">({</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">Key</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> key,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.visibleExtent </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">required</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.builder,</span></span>
<span class="line"><span style="color:#24292E;">  }) </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">super</span><span style="color:#24292E;">(key</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> key);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SliverFlexibleHeaderBuilder</span><span style="color:#24292E;"> builder;</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> visibleExtent;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">build</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">_SliverFlexibleHeader</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      visibleExtent</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> visibleExtent,</span></span>
<span class="line"><span style="color:#24292E;">      child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">LayoutBuilder</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">        builder</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> (</span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context, </span><span style="color:#005CC5;">BoxConstraints</span><span style="color:#24292E;"> constraints) {</span></span>
<span class="line"><span style="color:#24292E;">          </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">builder</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">            context,</span></span>
<span class="line"><span style="color:#24292E;">            constraints.maxHeight,</span></span>
<span class="line"><span style="color:#24292E;">            </span><span style="color:#6A737D;">// 获取滑动方向</span></span>
<span class="line"><span style="color:#24292E;">            (constraints </span><span style="color:#D73A49;">as</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">ExtraInfoBoxConstraints</span><span style="color:#24292E;">&lt;</span><span style="color:#005CC5;">ScrollDirection</span><span style="color:#24292E;">&gt;).extra,</span></span>
<span class="line"><span style="color:#24292E;">          );</span></span>
<span class="line"><span style="color:#24292E;">        },</span></span>
<span class="line"><span style="color:#24292E;">      ),</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>最后我们看一下 SliverFlexibleHeader 中确定滑动方向的逻辑：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#6A737D;">// 下拉过程中overlap会一直变化.</span></span>
<span class="line"><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> overScroll </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> constraints.overlap </span><span style="color:#F97583;">&lt;</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> constraints.overlap.</span><span style="color:#B392F0;">abs</span><span style="color:#E1E4E8;">() </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#F97583;">var</span><span style="color:#E1E4E8;"> scrollOffset </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> constraints.scrollOffset;</span></span>
<span class="line"><span style="color:#E1E4E8;">_direction </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">ScrollDirection</span><span style="color:#E1E4E8;">.idle;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">// 根据前后的overScroll值之差确定列表滑动方向。注意，不能直接使用 constraints.userScrollDirection，</span></span>
<span class="line"><span style="color:#6A737D;">// 这是因为该参数只表示用户滑动操作的方向。比如当我们下拉超出边界时，然后松手，此时列表会弹回，即列表滚动</span></span>
<span class="line"><span style="color:#6A737D;">// 方向是向上，而此时用户操作已经结束，ScrollDirection 的方向是上一次的用户滑动方向(向下)，这是便有问题。</span></span>
<span class="line"><span style="color:#F97583;">var</span><span style="color:#E1E4E8;"> distance </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> overScroll </span><span style="color:#F97583;">&gt;</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> overScroll </span><span style="color:#F97583;">-</span><span style="color:#E1E4E8;"> _lastOverScroll</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> _lastScrollOffset </span><span style="color:#F97583;">-</span><span style="color:#E1E4E8;"> scrollOffset;</span></span>
<span class="line"><span style="color:#E1E4E8;">_lastOverScroll </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> overScroll;</span></span>
<span class="line"><span style="color:#E1E4E8;">_lastScrollOffset </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> scrollOffset;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (constraints.userScrollDirection </span><span style="color:#F97583;">==</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">ScrollDirection</span><span style="color:#E1E4E8;">.idle) {</span></span>
<span class="line"><span style="color:#E1E4E8;">  _direction </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">ScrollDirection</span><span style="color:#E1E4E8;">.idle;</span></span>
<span class="line"><span style="color:#E1E4E8;">  _lastOverScroll </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">} </span><span style="color:#F97583;">else</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (distance </span><span style="color:#F97583;">&gt;</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">) {</span></span>
<span class="line"><span style="color:#E1E4E8;">  _direction </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">ScrollDirection</span><span style="color:#E1E4E8;">.forward;</span></span>
<span class="line"><span style="color:#E1E4E8;">} </span><span style="color:#F97583;">else</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (distance </span><span style="color:#F97583;">&lt;</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">) {</span></span>
<span class="line"><span style="color:#E1E4E8;">  _direction </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">ScrollDirection</span><span style="color:#E1E4E8;">.reverse;</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#6A737D;">// 下拉过程中overlap会一直变化.</span></span>
<span class="line"><span style="color:#005CC5;">double</span><span style="color:#24292E;"> overScroll </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> constraints.overlap </span><span style="color:#D73A49;">&lt;</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> constraints.overlap.</span><span style="color:#6F42C1;">abs</span><span style="color:#24292E;">() </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#D73A49;">var</span><span style="color:#24292E;"> scrollOffset </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> constraints.scrollOffset;</span></span>
<span class="line"><span style="color:#24292E;">_direction </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">ScrollDirection</span><span style="color:#24292E;">.idle;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">// 根据前后的overScroll值之差确定列表滑动方向。注意，不能直接使用 constraints.userScrollDirection，</span></span>
<span class="line"><span style="color:#6A737D;">// 这是因为该参数只表示用户滑动操作的方向。比如当我们下拉超出边界时，然后松手，此时列表会弹回，即列表滚动</span></span>
<span class="line"><span style="color:#6A737D;">// 方向是向上，而此时用户操作已经结束，ScrollDirection 的方向是上一次的用户滑动方向(向下)，这是便有问题。</span></span>
<span class="line"><span style="color:#D73A49;">var</span><span style="color:#24292E;"> distance </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> overScroll </span><span style="color:#D73A49;">&gt;</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> overScroll </span><span style="color:#D73A49;">-</span><span style="color:#24292E;"> _lastOverScroll</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> _lastScrollOffset </span><span style="color:#D73A49;">-</span><span style="color:#24292E;"> scrollOffset;</span></span>
<span class="line"><span style="color:#24292E;">_lastOverScroll </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> overScroll;</span></span>
<span class="line"><span style="color:#24292E;">_lastScrollOffset </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> scrollOffset;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (constraints.userScrollDirection </span><span style="color:#D73A49;">==</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">ScrollDirection</span><span style="color:#24292E;">.idle) {</span></span>
<span class="line"><span style="color:#24292E;">  _direction </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">ScrollDirection</span><span style="color:#24292E;">.idle;</span></span>
<span class="line"><span style="color:#24292E;">  _lastOverScroll </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">} </span><span style="color:#D73A49;">else</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (distance </span><span style="color:#D73A49;">&gt;</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">) {</span></span>
<span class="line"><span style="color:#24292E;">  _direction </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">ScrollDirection</span><span style="color:#24292E;">.forward;</span></span>
<span class="line"><span style="color:#24292E;">} </span><span style="color:#D73A49;">else</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (distance </span><span style="color:#D73A49;">&lt;</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">) {</span></span>
<span class="line"><span style="color:#24292E;">  _direction </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">ScrollDirection</span><span style="color:#24292E;">.reverse;</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><h3 id="_3-高度修正-scrolloffsetcorrection" tabindex="-1">3. 高度修正 scrollOffsetCorrection <a class="header-anchor" href="#_3-高度修正-scrolloffsetcorrection" aria-label="Permalink to &quot;3. 高度修正 scrollOffsetCorrection&quot;">​</a></h3><p>如果 visibleExtent 变化时，我们看看效果，如图6-29所示：</p><p><img src="/assets/6-29.1b95ff23.gif" alt="图6-29"></p><p>可以看到有一个突兀地跳动，这是因为 visibleExtent 变化时会导致 layoutExtent 发生变化，也就是说 SliverFlexibleHeader 在屏幕中所占的布局高度会发生变化，所以列表就出现跳动。但这个跳动效果太突兀了，我们知道每一个 Sliver 的高度是通过 scrollExtent 属性预估出来的，因此我们需要修正一下 scrollExtent，但是我们不能直接修改 scrollExtent 的值，直接修改不会有任何动画效果，仍然会跳动，为此，SliverGeometry 提供了一个 scrollOffsetCorrection 属性，它专门用于修正 scrollExtent ，我们只需要将要修正差值传给scrollOffsetCorrection，然后 Sliver 会自动执行一个动画效果过渡到我们期望的高度。</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#6A737D;">// 是否需要修正scrollOffset。当_visibleExtent值更新后，为了防止</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// 视觉上突然地跳动，要先修正 scrollOffset。</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">double</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> _scrollOffsetCorrection;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">set</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">visibleExtent</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> value) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 可视长度发生变化，更新状态并重新布局</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (_visibleExtent </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> value) {</span></span>
<span class="line"><span style="color:#E1E4E8;">      _lastOverScroll </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">      _reported </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">false</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#6A737D;">// 计算修正值</span></span>
<span class="line"><span style="color:#E1E4E8;">      _scrollOffsetCorrection </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> value </span><span style="color:#F97583;">-</span><span style="color:#E1E4E8;"> _visibleExtent;</span></span>
<span class="line"><span style="color:#E1E4E8;">      _visibleExtent </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> value;</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#B392F0;">markNeedsLayout</span><span style="color:#E1E4E8;">();</span></span>
<span class="line"><span style="color:#E1E4E8;">    }</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">performLayout</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// _visibleExtent 值更新后，为了防止突然的跳动，先修正 scrollOffset</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (_scrollOffsetCorrection </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">null</span><span style="color:#E1E4E8;">) {</span></span>
<span class="line"><span style="color:#E1E4E8;">      geometry </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SliverGeometry</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#6A737D;">//修正</span></span>
<span class="line"><span style="color:#E1E4E8;">        scrollOffsetCorrection</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> _scrollOffsetCorrection,</span></span>
<span class="line"><span style="color:#E1E4E8;">      );</span></span>
<span class="line"><span style="color:#E1E4E8;">      _scrollOffsetCorrection </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">null</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">    }</span></span>
<span class="line"><span style="color:#E1E4E8;">    ...</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#6A737D;">// 是否需要修正scrollOffset。当_visibleExtent值更新后，为了防止</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// 视觉上突然地跳动，要先修正 scrollOffset。</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">double</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> _scrollOffsetCorrection;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">set</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">visibleExtent</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> value) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 可视长度发生变化，更新状态并重新布局</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (_visibleExtent </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> value) {</span></span>
<span class="line"><span style="color:#24292E;">      _lastOverScroll </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">      _reported </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">false</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6A737D;">// 计算修正值</span></span>
<span class="line"><span style="color:#24292E;">      _scrollOffsetCorrection </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> value </span><span style="color:#D73A49;">-</span><span style="color:#24292E;"> _visibleExtent;</span></span>
<span class="line"><span style="color:#24292E;">      _visibleExtent </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> value;</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6F42C1;">markNeedsLayout</span><span style="color:#24292E;">();</span></span>
<span class="line"><span style="color:#24292E;">    }</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">performLayout</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// _visibleExtent 值更新后，为了防止突然的跳动，先修正 scrollOffset</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (_scrollOffsetCorrection </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">null</span><span style="color:#24292E;">) {</span></span>
<span class="line"><span style="color:#24292E;">      geometry </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SliverGeometry</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#6A737D;">//修正</span></span>
<span class="line"><span style="color:#24292E;">        scrollOffsetCorrection</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> _scrollOffsetCorrection,</span></span>
<span class="line"><span style="color:#24292E;">      );</span></span>
<span class="line"><span style="color:#24292E;">      _scrollOffsetCorrection </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">null</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#D73A49;">return</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">    }</span></span>
<span class="line"><span style="color:#24292E;">    ...</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span></code></pre></div><p>运行后效果如图6-30（动图可能太快，可以直接运行示例查看效果）：</p><p><img src="/assets/6-30.fd55f9a0.gif" alt="图6-30"></p><h3 id="_4-边界" tabindex="-1">4. 边界 <a class="header-anchor" href="#_4-边界" aria-label="Permalink to &quot;4. 边界&quot;">​</a></h3><p>在 SliverFlexibleHeader 构建子组件时开发者可能会依赖“当前的可用高度是否为0”来做一些特殊处理，比如记录是否子组件已经离开了屏幕。但是根据上面的实现，当用户滑动非常快时，子组件离开屏幕时的最后一次布局时传递的约束的 maxExtent 可能不为 0，而当 constraints.scrollOffset 大于 _visibleExtent 时我们在 performLayout 的一开始就返回了，因此 LayoutBuilder 的 builder 中就有可能收不到 maxExtent 为 0 时的回调。为了解决这个问题，我们只需要在每次 Sliver 离开屏幕时调用一次 child.layout 同时 将maxExtent 指定为 0 即可，为此我们修改一下：</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">performLayout</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (child </span><span style="color:#F97583;">==</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">null</span><span style="color:#E1E4E8;">) {</span></span>
<span class="line"><span style="color:#E1E4E8;">      geometry </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SliverGeometry</span><span style="color:#E1E4E8;">(scrollExtent</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> _visibleExtent);</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">    }</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//当已经完全滑出屏幕时</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (constraints.scrollOffset </span><span style="color:#F97583;">&gt;</span><span style="color:#E1E4E8;"> _visibleExtent) {</span></span>
<span class="line"><span style="color:#E1E4E8;">      geometry </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SliverGeometry</span><span style="color:#E1E4E8;">(scrollExtent</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> _visibleExtent);</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#6A737D;">// 通知 child 重新布局，注意，通知一次即可，如果不通知，滑出屏幕后，child 在最后</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#6A737D;">// 一次构建时拿到的可用高度可能不为 0。因为使用者在构建子节点的时候，可能会依赖</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#6A737D;">// &quot;当前的可用高度是否为0&quot; 来做一些特殊处理，比如记录是否子节点已经离开了屏幕，</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#6A737D;">// 因此，我们需要在离开屏幕时确保LayoutBuilder的builder会被调用一次（构建子组件）。</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">_reported) {</span></span>
<span class="line"><span style="color:#E1E4E8;">        _reported </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">true</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">        child</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">layout</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">          </span><span style="color:#79B8FF;">ExtraInfoBoxConstraints</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">            _direction, </span><span style="color:#6A737D;">//传递滑动方向</span></span>
<span class="line"><span style="color:#E1E4E8;">            constraints.</span><span style="color:#B392F0;">asBoxConstraints</span><span style="color:#E1E4E8;">(maxExtent</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">),</span></span>
<span class="line"><span style="color:#E1E4E8;">          ),</span></span>
<span class="line"><span style="color:#E1E4E8;">          </span><span style="color:#6A737D;">//我们不会使用自节点的 Size, 关于此参数更详细的内容见本书后面关于layout原理的介绍</span></span>
<span class="line"><span style="color:#E1E4E8;">          parentUsesSize</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">false</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">        );</span></span>
<span class="line"><span style="color:#E1E4E8;">      }</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">//子组件回到了屏幕中，重置通知状态</span></span>
<span class="line"><span style="color:#E1E4E8;">    _reported </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">false</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span></span>
<span class="line"><span style="color:#E1E4E8;">  ...</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">performLayout</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (child </span><span style="color:#D73A49;">==</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">null</span><span style="color:#24292E;">) {</span></span>
<span class="line"><span style="color:#24292E;">      geometry </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SliverGeometry</span><span style="color:#24292E;">(scrollExtent</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> _visibleExtent);</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#D73A49;">return</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">    }</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//当已经完全滑出屏幕时</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (constraints.scrollOffset </span><span style="color:#D73A49;">&gt;</span><span style="color:#24292E;"> _visibleExtent) {</span></span>
<span class="line"><span style="color:#24292E;">      geometry </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SliverGeometry</span><span style="color:#24292E;">(scrollExtent</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> _visibleExtent);</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6A737D;">// 通知 child 重新布局，注意，通知一次即可，如果不通知，滑出屏幕后，child 在最后</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6A737D;">// 一次构建时拿到的可用高度可能不为 0。因为使用者在构建子节点的时候，可能会依赖</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6A737D;">// &quot;当前的可用高度是否为0&quot; 来做一些特殊处理，比如记录是否子节点已经离开了屏幕，</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6A737D;">// 因此，我们需要在离开屏幕时确保LayoutBuilder的builder会被调用一次（构建子组件）。</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">_reported) {</span></span>
<span class="line"><span style="color:#24292E;">        _reported </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">true</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">        child</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">layout</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">          </span><span style="color:#005CC5;">ExtraInfoBoxConstraints</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">            _direction, </span><span style="color:#6A737D;">//传递滑动方向</span></span>
<span class="line"><span style="color:#24292E;">            constraints.</span><span style="color:#6F42C1;">asBoxConstraints</span><span style="color:#24292E;">(maxExtent</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">),</span></span>
<span class="line"><span style="color:#24292E;">          ),</span></span>
<span class="line"><span style="color:#24292E;">          </span><span style="color:#6A737D;">//我们不会使用自节点的 Size, 关于此参数更详细的内容见本书后面关于layout原理的介绍</span></span>
<span class="line"><span style="color:#24292E;">          parentUsesSize</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">false</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">        );</span></span>
<span class="line"><span style="color:#24292E;">      }</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#D73A49;">return</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">//子组件回到了屏幕中，重置通知状态</span></span>
<span class="line"><span style="color:#24292E;">    _reported </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">false</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">  </span></span>
<span class="line"><span style="color:#24292E;">  ...</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>至此大功告成！</p><h2 id="_6-11-3-自定义-sliver-二-sliverpersistentheadertobox" tabindex="-1">6.11.3 自定义 Sliver（二）SliverPersistentHeaderToBox <a class="header-anchor" href="#_6-11-3-自定义-sliver-二-sliverpersistentheadertobox" aria-label="Permalink to &quot;6.11.3 自定义 Sliver（二）SliverPersistentHeaderToBox&quot;">​</a></h2><p>我们在上一节介绍了 SliverPersistentHeader，在使用时需要遵守两个规则 ：</p><ol><li><p>必须显式指定高度。</p></li><li><p>如果我们在使用 SliverPersistentHeader 构建子组件时需要依赖 overlapsContent 参数，则必须保证之前至少还有一个 SliverPersistentHeader 或 SliverAppBar。</p></li></ol><p>遵守上面这两条规则对于开发者来说心智负担还是较重的，比如对于规则 1，大多数时候我们是不知道 Header 具体的高度的，我们期望直接传一个 widget ，这个 widget 的实际高度 SliverPersistentHeader 能自动算出来。对于规则 2 就更不用说，不知道这个准是要踩坑的。综上，本节我们自定义一个 SliverPersistentHeaderToBox，它可以将任意 RenderBox 适配为可以固定到顶部的 Sliver 而不用显式指定高度，同时避免上面的问题 2。</p><p>第一步：我们先看一下定义 SliverPersistentHeaderToBox。</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">typedef</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SliverPersistentHeaderToBoxBuilder</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Function</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context,</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> maxExtent, </span><span style="color:#6A737D;">//当前可用最大高度</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">bool</span><span style="color:#E1E4E8;"> fixed, </span><span style="color:#6A737D;">// 是否已经固定</span></span>
<span class="line"><span style="color:#E1E4E8;">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SliverPersistentHeaderToBox</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">StatelessWidget</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// 默认构造函数，直接接受一个 widget，不用显式指定高度</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">SliverPersistentHeaderToBox</span><span style="color:#E1E4E8;">({</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">Key</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> key,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">required</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> child,</span></span>
<span class="line"><span style="color:#E1E4E8;">  })  </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> builder </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> ((a, b, c) </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> child),</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#79B8FF;">super</span><span style="color:#E1E4E8;">(key</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> key);</span></span>
<span class="line"><span style="color:#E1E4E8;"> </span><span style="color:#6A737D;">// builder 构造函数，需要传一个 builder，同样不需要显式指定高度</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">SliverPersistentHeaderToBox</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">builder</span><span style="color:#E1E4E8;">({</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">Key</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> key,</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">required</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">this</span><span style="color:#E1E4E8;">.builder,</span></span>
<span class="line"><span style="color:#E1E4E8;">  }) </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">super</span><span style="color:#E1E4E8;">(key</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> key);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">final</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SliverPersistentHeaderToBoxBuilder</span><span style="color:#E1E4E8;"> builder;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">build</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">_SliverPersistentHeaderToBox</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#6A737D;">// 通过 LayoutBuilder接收 Sliver 传递给子组件的布局约束信息</span></span>
<span class="line"><span style="color:#E1E4E8;">      child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">LayoutBuilder</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">        builder</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> (</span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context, </span><span style="color:#79B8FF;">BoxConstraints</span><span style="color:#E1E4E8;"> constraints) {</span></span>
<span class="line"><span style="color:#E1E4E8;">          </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">builder</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">            context,</span></span>
<span class="line"><span style="color:#E1E4E8;">            constraints.maxHeight,</span></span>
<span class="line"><span style="color:#E1E4E8;">            </span><span style="color:#6A737D;">//约束中需要传递的额外信息是一个bool类型，表示 Sliver 是否已经固定到顶部</span></span>
<span class="line"><span style="color:#E1E4E8;">            (constraints </span><span style="color:#F97583;">as</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">ExtraInfoBoxConstraints</span><span style="color:#E1E4E8;">&lt;</span><span style="color:#79B8FF;">bool</span><span style="color:#E1E4E8;">&gt;).extra,</span></span>
<span class="line"><span style="color:#E1E4E8;">          );</span></span>
<span class="line"><span style="color:#E1E4E8;">        },</span></span>
<span class="line"><span style="color:#E1E4E8;">      ),</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">typedef</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SliverPersistentHeaderToBoxBuilder</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Function</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context,</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> maxExtent, </span><span style="color:#6A737D;">//当前可用最大高度</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">bool</span><span style="color:#24292E;"> fixed, </span><span style="color:#6A737D;">// 是否已经固定</span></span>
<span class="line"><span style="color:#24292E;">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SliverPersistentHeaderToBox</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">StatelessWidget</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// 默认构造函数，直接接受一个 widget，不用显式指定高度</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">SliverPersistentHeaderToBox</span><span style="color:#24292E;">({</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">Key</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> key,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">required</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> child,</span></span>
<span class="line"><span style="color:#24292E;">  })  </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> builder </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> ((a, b, c) </span><span style="color:#D73A49;">=&gt;</span><span style="color:#24292E;"> child),</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#005CC5;">super</span><span style="color:#24292E;">(key</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> key);</span></span>
<span class="line"><span style="color:#24292E;"> </span><span style="color:#6A737D;">// builder 构造函数，需要传一个 builder，同样不需要显式指定高度</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">SliverPersistentHeaderToBox</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">builder</span><span style="color:#24292E;">({</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">Key</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> key,</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">required</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">this</span><span style="color:#24292E;">.builder,</span></span>
<span class="line"><span style="color:#24292E;">  }) </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">super</span><span style="color:#24292E;">(key</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> key);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">final</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SliverPersistentHeaderToBoxBuilder</span><span style="color:#24292E;"> builder;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">build</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">_SliverPersistentHeaderToBox</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6A737D;">// 通过 LayoutBuilder接收 Sliver 传递给子组件的布局约束信息</span></span>
<span class="line"><span style="color:#24292E;">      child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">LayoutBuilder</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">        builder</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> (</span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context, </span><span style="color:#005CC5;">BoxConstraints</span><span style="color:#24292E;"> constraints) {</span></span>
<span class="line"><span style="color:#24292E;">          </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">builder</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">            context,</span></span>
<span class="line"><span style="color:#24292E;">            constraints.maxHeight,</span></span>
<span class="line"><span style="color:#24292E;">            </span><span style="color:#6A737D;">//约束中需要传递的额外信息是一个bool类型，表示 Sliver 是否已经固定到顶部</span></span>
<span class="line"><span style="color:#24292E;">            (constraints </span><span style="color:#D73A49;">as</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">ExtraInfoBoxConstraints</span><span style="color:#24292E;">&lt;</span><span style="color:#005CC5;">bool</span><span style="color:#24292E;">&gt;).extra,</span></span>
<span class="line"><span style="color:#24292E;">          );</span></span>
<span class="line"><span style="color:#24292E;">        },</span></span>
<span class="line"><span style="color:#24292E;">      ),</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>和上面的 SliverFlexibleHeader 很像，不同的是SliverPersistentHeaderToBox传递给 child 的约束中的额外信息是一个 bool 类型，表示是否已经固定到顶部。</p><p>第二步：实现 _SliverPersistentHeaderToBox。</p><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">_RenderSliverPersistentHeaderToBox</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">RenderSliverSingleBoxAdapter</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">void</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">performLayout</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">if</span><span style="color:#E1E4E8;"> (child </span><span style="color:#F97583;">==</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">null</span><span style="color:#E1E4E8;">) {</span></span>
<span class="line"><span style="color:#E1E4E8;">      geometry </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SliverGeometry</span><span style="color:#E1E4E8;">.zero;</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">    }</span></span>
<span class="line"><span style="color:#E1E4E8;">    child</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">layout</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#79B8FF;">ExtraInfoBoxConstraints</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#6A737D;">//只要 constraints.scrollOffset不为0，则表示已经有内容在当前Sliver下面了，即已经固定到顶部了</span></span>
<span class="line"><span style="color:#E1E4E8;">        constraints.scrollOffset </span><span style="color:#F97583;">!=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">        constraints.</span><span style="color:#B392F0;">asBoxConstraints</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">          </span><span style="color:#6A737D;">// 我们将剩余的可绘制空间作为 header 的最大高度约束传递给 LayoutBuilder</span></span>
<span class="line"><span style="color:#E1E4E8;">          maxExtent</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> constraints.remainingPaintExtent,</span></span>
<span class="line"><span style="color:#E1E4E8;">        ),</span></span>
<span class="line"><span style="color:#E1E4E8;">      ),</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#6A737D;">//我们要根据child大小来确定Sliver大小，所以后面需要用到child的大小（size）信息</span></span>
<span class="line"><span style="color:#E1E4E8;">      parentUsesSize</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">true</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 子节点 layout 后就能获取它的大小了</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> childExtent;</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">switch</span><span style="color:#E1E4E8;"> (constraints.axis) {</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#F97583;">case</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Axis</span><span style="color:#E1E4E8;">.horizontal</span><span style="color:#F97583;">:</span></span>
<span class="line"><span style="color:#E1E4E8;">        childExtent </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> child</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">.size.width;</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#F97583;">break</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#F97583;">case</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Axis</span><span style="color:#E1E4E8;">.vertical</span><span style="color:#F97583;">:</span></span>
<span class="line"><span style="color:#E1E4E8;">        childExtent </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> child</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">.size.height;</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#F97583;">break</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">    geometry </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SliverGeometry</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      scrollExtent</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> childExtent,</span></span>
<span class="line"><span style="color:#E1E4E8;">      paintOrigin</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">, </span><span style="color:#6A737D;">// 固定，如果不想固定应该传` - constraints.scrollOffset`</span></span>
<span class="line"><span style="color:#E1E4E8;">      paintExtent</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> childExtent,</span></span>
<span class="line"><span style="color:#E1E4E8;">      maxPaintExtent</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> childExtent,</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// 重要，必须重写，下面介绍。</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">double</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">childMainAxisPosition</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">RenderBox</span><span style="color:#E1E4E8;"> child) </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0.0</span><span style="color:#E1E4E8;">;</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">_RenderSliverPersistentHeaderToBox</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">RenderSliverSingleBoxAdapter</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">void</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">performLayout</span><span style="color:#24292E;">() {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">if</span><span style="color:#24292E;"> (child </span><span style="color:#D73A49;">==</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">null</span><span style="color:#24292E;">) {</span></span>
<span class="line"><span style="color:#24292E;">      geometry </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SliverGeometry</span><span style="color:#24292E;">.zero;</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#D73A49;">return</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">    }</span></span>
<span class="line"><span style="color:#24292E;">    child</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">layout</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#005CC5;">ExtraInfoBoxConstraints</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#6A737D;">//只要 constraints.scrollOffset不为0，则表示已经有内容在当前Sliver下面了，即已经固定到顶部了</span></span>
<span class="line"><span style="color:#24292E;">        constraints.scrollOffset </span><span style="color:#D73A49;">!=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">        constraints.</span><span style="color:#6F42C1;">asBoxConstraints</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">          </span><span style="color:#6A737D;">// 我们将剩余的可绘制空间作为 header 的最大高度约束传递给 LayoutBuilder</span></span>
<span class="line"><span style="color:#24292E;">          maxExtent</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> constraints.remainingPaintExtent,</span></span>
<span class="line"><span style="color:#24292E;">        ),</span></span>
<span class="line"><span style="color:#24292E;">      ),</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#6A737D;">//我们要根据child大小来确定Sliver大小，所以后面需要用到child的大小（size）信息</span></span>
<span class="line"><span style="color:#24292E;">      parentUsesSize</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">true</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 子节点 layout 后就能获取它的大小了</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> childExtent;</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">switch</span><span style="color:#24292E;"> (constraints.axis) {</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#D73A49;">case</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Axis</span><span style="color:#24292E;">.horizontal</span><span style="color:#D73A49;">:</span></span>
<span class="line"><span style="color:#24292E;">        childExtent </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> child</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">.size.width;</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#D73A49;">break</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#D73A49;">case</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Axis</span><span style="color:#24292E;">.vertical</span><span style="color:#D73A49;">:</span></span>
<span class="line"><span style="color:#24292E;">        childExtent </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> child</span><span style="color:#D73A49;">!</span><span style="color:#24292E;">.size.height;</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#D73A49;">break</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">    geometry </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SliverGeometry</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      scrollExtent</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> childExtent,</span></span>
<span class="line"><span style="color:#24292E;">      paintOrigin</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">, </span><span style="color:#6A737D;">// 固定，如果不想固定应该传` - constraints.scrollOffset`</span></span>
<span class="line"><span style="color:#24292E;">      paintExtent</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> childExtent,</span></span>
<span class="line"><span style="color:#24292E;">      maxPaintExtent</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> childExtent,</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// 重要，必须重写，下面介绍。</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">double</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">childMainAxisPosition</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">RenderBox</span><span style="color:#24292E;"> child) </span><span style="color:#D73A49;">=&gt;</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0.0</span><span style="color:#24292E;">;</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>上面代码有四点需要注意：</p><ol><li>constraints.scrollOffset 不为 0 时，则表示已经固定到顶部了。</li><li>我们在布局阶段拿到子组件的 size 信息，然后通过通过子组件的大小来确定 Sliver 大小（设置geometry）。 这样就不再需要我们显式传高度值了。</li><li>我们通过给 paintOrigin 设为 0 来实现顶部固定效果；不固定到顶部时应该传 <code> - constraints.scrollOffset</code>，这个需要读者好好体会一下，也可以运行示例修改一下参数值来看看效果。</li><li>必须要重写 <code>childMainAxisPosition</code> ，否则事件便会失效，该方法的返回值在“点击测试”中会用到。关于点击测试我们会在8.1节中介绍， 读者现在只需要知道该函数应该返回 paintOrigin 的位置即可。</li></ol><p>大功告成！下面我们来测试一下！我们创建两个 header：</p><ol><li>第一个 header：当没有滑动到顶部时，外观和正常列表项一样；当固定到顶部后，显示一个阴影。为了实现这个效果我们需要通过 SliverPersistentHeaderToBox.builder 来动态创建。</li><li>第二个 header: 一个普通的列表项，它接受一个 widget。</li></ol><div class="language-dart vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre class="shiki github-dark vp-code-dark"><code><span class="line"><span style="color:#F97583;">class</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SliverPersistentHeaderToBoxRoute</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">extends</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">StatelessWidget</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">const</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">SliverPersistentHeaderToBoxRoute</span><span style="color:#E1E4E8;">({</span><span style="color:#79B8FF;">Key</span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> key}) </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">super</span><span style="color:#E1E4E8;">(key</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> key);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#F97583;">@override</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">build</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">BuildContext</span><span style="color:#E1E4E8;"> context) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">CustomScrollView</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      slivers</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> [</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#B392F0;">buildSliverList</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">5</span><span style="color:#E1E4E8;">),</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#79B8FF;">SliverPersistentHeaderToBox</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">builder</span><span style="color:#E1E4E8;">(builder</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> headerBuilder),</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#B392F0;">buildSliverList</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">5</span><span style="color:#E1E4E8;">),</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#79B8FF;">SliverPersistentHeaderToBox</span><span style="color:#E1E4E8;">(child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">wTitle</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">&#39;Title 2&#39;</span><span style="color:#E1E4E8;">)),</span></span>
<span class="line"><span style="color:#E1E4E8;">        </span><span style="color:#B392F0;">buildSliverList</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">50</span><span style="color:#E1E4E8;">),</span></span>
<span class="line"><span style="color:#E1E4E8;">      ],</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// 当 header 固定后显示阴影</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">headerBuilder</span><span style="color:#E1E4E8;">(context, maxExtent, fixed) {</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 获取当前应用主题，关于主题相关内容将在后面章节介绍，现在</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#6A737D;">// 我们要从主题中获取一些颜色。</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">var</span><span style="color:#E1E4E8;"> theme </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Theme</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">of</span><span style="color:#E1E4E8;">(context);</span></span>
<span class="line"><span style="color:#E1E4E8;">    </span><span style="color:#F97583;">return</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Material</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">      child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Container</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#E1E4E8;">        color</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> fixed </span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Colors</span><span style="color:#E1E4E8;">.white </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> theme.canvasColor,</span></span>
<span class="line"><span style="color:#E1E4E8;">        child</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">wTitle</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">&#39;Title 1&#39;</span><span style="color:#E1E4E8;">),</span></span>
<span class="line"><span style="color:#E1E4E8;">      ),</span></span>
<span class="line"><span style="color:#E1E4E8;">      elevation</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> fixed </span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">4</span><span style="color:#E1E4E8;"> </span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">0</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">      shadowColor</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> theme.appBarTheme.shadowColor,</span></span>
<span class="line"><span style="color:#E1E4E8;">    );</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#6A737D;">// 我们约定小写字母 w 开头的函数代表是需要构建一个 Widget，这比 buildXX 会更简洁</span></span>
<span class="line"><span style="color:#E1E4E8;">  </span><span style="color:#79B8FF;">Widget</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">wTitle</span><span style="color:#E1E4E8;">(</span><span style="color:#79B8FF;">String</span><span style="color:#E1E4E8;"> text) </span><span style="color:#F97583;">=&gt;</span></span>
<span class="line"><span style="color:#E1E4E8;">      </span><span style="color:#79B8FF;">ListTile</span><span style="color:#E1E4E8;">(title</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> </span><span style="color:#79B8FF;">Text</span><span style="color:#E1E4E8;">(text), onTap</span><span style="color:#F97583;">:</span><span style="color:#E1E4E8;"> () </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> </span><span style="color:#B392F0;">print</span><span style="color:#E1E4E8;">(text));</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre><pre class="shiki github-light vp-code-light"><code><span class="line"><span style="color:#D73A49;">class</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SliverPersistentHeaderToBoxRoute</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">extends</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">StatelessWidget</span><span style="color:#24292E;"> {</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">const</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">SliverPersistentHeaderToBoxRoute</span><span style="color:#24292E;">({</span><span style="color:#005CC5;">Key</span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> key}) </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">super</span><span style="color:#24292E;">(key</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> key);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#D73A49;">@override</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">build</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">BuildContext</span><span style="color:#24292E;"> context) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">CustomScrollView</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      slivers</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> [</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#6F42C1;">buildSliverList</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">5</span><span style="color:#24292E;">),</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#005CC5;">SliverPersistentHeaderToBox</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">builder</span><span style="color:#24292E;">(builder</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> headerBuilder),</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#6F42C1;">buildSliverList</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">5</span><span style="color:#24292E;">),</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#005CC5;">SliverPersistentHeaderToBox</span><span style="color:#24292E;">(child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">wTitle</span><span style="color:#24292E;">(</span><span style="color:#032F62;">&#39;Title 2&#39;</span><span style="color:#24292E;">)),</span></span>
<span class="line"><span style="color:#24292E;">        </span><span style="color:#6F42C1;">buildSliverList</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">50</span><span style="color:#24292E;">),</span></span>
<span class="line"><span style="color:#24292E;">      ],</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// 当 header 固定后显示阴影</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">headerBuilder</span><span style="color:#24292E;">(context, maxExtent, fixed) {</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 获取当前应用主题，关于主题相关内容将在后面章节介绍，现在</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#6A737D;">// 我们要从主题中获取一些颜色。</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">var</span><span style="color:#24292E;"> theme </span><span style="color:#D73A49;">=</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Theme</span><span style="color:#24292E;">.</span><span style="color:#6F42C1;">of</span><span style="color:#24292E;">(context);</span></span>
<span class="line"><span style="color:#24292E;">    </span><span style="color:#D73A49;">return</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Material</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">      child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Container</span><span style="color:#24292E;">(</span></span>
<span class="line"><span style="color:#24292E;">        color</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> fixed </span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Colors</span><span style="color:#24292E;">.white </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> theme.canvasColor,</span></span>
<span class="line"><span style="color:#24292E;">        child</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">wTitle</span><span style="color:#24292E;">(</span><span style="color:#032F62;">&#39;Title 1&#39;</span><span style="color:#24292E;">),</span></span>
<span class="line"><span style="color:#24292E;">      ),</span></span>
<span class="line"><span style="color:#24292E;">      elevation</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> fixed </span><span style="color:#D73A49;">?</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">4</span><span style="color:#24292E;"> </span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">0</span><span style="color:#24292E;">,</span></span>
<span class="line"><span style="color:#24292E;">      shadowColor</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> theme.appBarTheme.shadowColor,</span></span>
<span class="line"><span style="color:#24292E;">    );</span></span>
<span class="line"><span style="color:#24292E;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#6A737D;">// 我们约定小写字母 w 开头的函数代表是需要构建一个 Widget，这比 buildXX 会更简洁</span></span>
<span class="line"><span style="color:#24292E;">  </span><span style="color:#005CC5;">Widget</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">wTitle</span><span style="color:#24292E;">(</span><span style="color:#005CC5;">String</span><span style="color:#24292E;"> text) </span><span style="color:#D73A49;">=&gt;</span></span>
<span class="line"><span style="color:#24292E;">      </span><span style="color:#005CC5;">ListTile</span><span style="color:#24292E;">(title</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> </span><span style="color:#005CC5;">Text</span><span style="color:#24292E;">(text), onTap</span><span style="color:#D73A49;">:</span><span style="color:#24292E;"> () </span><span style="color:#D73A49;">=&gt;</span><span style="color:#24292E;"> </span><span style="color:#6F42C1;">print</span><span style="color:#24292E;">(text));</span></span>
<span class="line"><span style="color:#24292E;">}</span></span></code></pre></div><p>运行效果如图6-31：</p><p><img src="/assets/6-31.62d3c78d.gif" alt="图6-31"></p><p>我们实现的 SliverPersistentHeaderToBox 不仅不需要显式指定高度，而且它的 builder 函数的第三个参数值也正常了（和SliverPersistentHeaderToBox 数量无关）。</p><h3 id="注意" tabindex="-1">注意 <a class="header-anchor" href="#注意" aria-label="Permalink to &quot;注意&quot;">​</a></h3><p>如果我们要使用 SliverAppBar，则建议使用 SliverPersistentHeader ，因为 SliverPersistentHeader 设计的初衷就是为了实现 SliverAppBar，所以它们一起使用时会有更好的协同。如果将 SliverPersistentHeaderToBox 和 SliverAppBar 一起使用，则可能又会导致其他问题，所以建议就是：<strong>在没有使用 SliverAppBar 时，用 SliverPersistentHeaderToBox，如果使用了 SliverAppBar ，用SliverPersistentHeader</strong>。</p><h2 id="_6-11-4-总结" tabindex="-1">6.11.4 总结 <a class="header-anchor" href="#_6-11-4-总结" aria-label="Permalink to &quot;6.11.4 总结&quot;">​</a></h2><p>本节先介绍了 Sliver 布局模型，然后对比了和 盒布局模型的区别，至此 Flutter 中的两种布局模型就都介绍了。然后通过自定义 SliverFlexibleHeader 和 SliverPersistentHeaderToBox 两个 Sliver 来演示了自定义 Sliver 的步骤，同时加深了对 Sliver 布局的理解。</p><p>这里需要提醒读者，大多数应用的大多数页面都会涉及到滚动列表，因此理解并掌握可滚动组件和 Sliver 布局协议原理很有必要。</p><p>另外，笔者将SliverFlexibleHeader、ExtraInfoBoxConstraints 以及 SliverPersistentHeaderToBox 都收集到了<a href="https://github.com/flutterchina/flukit/blob/main/package_src/lib/src/sliver_flexible_header.dart" target="_blank" rel="noreferrer">flukit组件库 </a>中，完整代码读者可以在flukit项目源码中找到。</p></div></div></main><footer class="VPDocFooter" data-v-6b87e69f data-v-ef5dee53><!--[--><!--]--><div class="edit-info" data-v-ef5dee53><div class="edit-link" data-v-ef5dee53><a class="VPLink link vp-external-link-icon no-icon edit-link-button" href="https://gitee.com/luoguangguang/note/tree/master/docs/largeFrontEnd/flutter/chapter6/sliver.md" target="_blank" rel="noreferrer" data-v-ef5dee53><!--[--><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="edit-link-icon" aria-label="edit icon" data-v-ef5dee53><path d="M18,23H4c-1.7,0-3-1.3-3-3V6c0-1.7,1.3-3,3-3h7c0.6,0,1,0.4,1,1s-0.4,1-1,1H4C3.4,5,3,5.4,3,6v14c0,0.6,0.4,1,1,1h14c0.6,0,1-0.4,1-1v-7c0-0.6,0.4-1,1-1s1,0.4,1,1v7C21,21.7,19.7,23,18,23z"></path><path d="M8,17c-0.3,0-0.5-0.1-0.7-0.3C7,16.5,6.9,16.1,7,15.8l1-4c0-0.2,0.1-0.3,0.3-0.5l9.5-9.5c1.2-1.2,3.2-1.2,4.4,0c1.2,1.2,1.2,3.2,0,4.4l-9.5,9.5c-0.1,0.1-0.3,0.2-0.5,0.3l-4,1C8.2,17,8.1,17,8,17zM9.9,12.5l-0.5,2.1l2.1-0.5l9.3-9.3c0.4-0.4,0.4-1.1,0-1.6c-0.4-0.4-1.2-0.4-1.6,0l0,0L9.9,12.5z M18.5,2.5L18.5,2.5L18.5,2.5z"></path></svg> 在Gitee上参与编辑此页<!--]--></a></div><!----></div><nav class="prev-next" data-v-ef5dee53><div class="pager" data-v-ef5dee53><!----></div><div class="pager" data-v-ef5dee53><a class="pager-link next" href="./largeFrontEnd/flutter/chapter1/index.html" data-v-ef5dee53><span class="desc" data-v-ef5dee53>Next page</span><span class="title" data-v-ef5dee53>第一章 初识Flutter</span></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><footer class="VPFooter has-sidebar" data-v-1919c326 data-v-e03eb2e1><div class="container" data-v-e03eb2e1><p class="message" data-v-e03eb2e1>Released under the MIT License.</p><p class="copyright" data-v-e03eb2e1>Copyright © 2023-present LG of GZU</p></div></footer><!--[--><!--]--></div></div>
    <script>window.__VP_HASH_MAP__=JSON.parse("{\"backend_index.md\":\"0cb07c7c\",\"largefrontend_electron_index.md\":\"adab8e13\",\"largefrontend_flutter_chapter1_index.md\":\"5106cc23\",\"index.md\":\"20a66851\",\"largefrontend_flutter_chapter10_intro.md\":\"5756d1e8\",\"largefrontend_flutter_chapter10_index.md\":\"87e47f16\",\"largefrontend_flutter_chapter1_flutter_intro.md\":\"9ad5fc78\",\"largefrontend_flutter_chapter12_package_and_plugin.md\":\"c2f14cd7\",\"largefrontend_flutter_chapter12_flutter_web.md\":\"f4b6b743\",\"largefrontend_flutter_chapter10_combine.md\":\"333319d8\",\"largefrontend_flutter_chapter14_flutter_ui_system.md\":\"a1011f7b\",\"largefrontend_flutter_chapter11_index.md\":\"35dcdd7c\",\"largefrontend_flutter_chapter12_index.md\":\"890b799c\",\"largefrontend_flutter_chapter13_index.md\":\"6db9b579\",\"largefrontend_flutter_chapter14_index.md\":\"87f7da6b\",\"largefrontend_flutter_chapter1_mobile_development_intro.md\":\"40c15ac2\",\"largefrontend_flutter_chapter10_turn_box.md\":\"6a792fe1\",\"largefrontend_flutter_chapter3_index.md\":\"bc1d0de5\",\"largefrontend_flutter_chapter4_stack.md\":\"3f5c3186\",\"largefrontend_flutter_chapter1_install_flutter.md\":\"4543c368\",\"largefrontend_flutter_chapter14_paint.md\":\"8f7289b0\",\"largefrontend_flutter_chapter5_padding.md\":\"9fadce05\",\"largefrontend_flutter_chapter4_index.md\":\"f9b298f7\",\"largefrontend_flutter_chapter2_flutter_package_mgr.md\":\"4a3b7161\",\"largefrontend_flutter_chapter11_socket.md\":\"e8264e44\",\"largefrontend_flutter_chapter4_intro.md\":\"1341b5fd\",\"largefrontend_flutter_chapter14_update.md\":\"e4d2c2f0\",\"largefrontend_flutter_chapter5_index.md\":\"455baa0a\",\"largefrontend_flutter_chapter4_alignment.md\":\"a6596610\",\"largefrontend_flutter_chapter13_multi_languages_support.md\":\"c113238d\",\"largefrontend_flutter_chapter11_dio.md\":\"64f4e813\",\"largefrontend_flutter_chapter2_first_flutter_app.md\":\"b650181d\",\"largefrontend_flutter_chapter2_flutter_app_debug.md\":\"67a72433\",\"largefrontend_flutter_chapter2_state_manage.md\":\"9bb80d37\",\"largefrontend_flutter_chapter6_index.md\":\"5a321b81\",\"largefrontend_flutter_chapter5_material_scaffold.md\":\"8e6b896a\",\"largefrontend_flutter_chapter3_buttons.md\":\"ef950360\",\"largefrontend_flutter_chapter10_done_widget.md\":\"c3b44318\",\"largefrontend_flutter_chapter6_single_child_scrollview.md\":\"e3f75f3b\",\"largefrontend_flutter_chapter14_paint_flow.md\":\"d2f8102a\",\"largefrontend_flutter_chapter6_intro.md\":\"125a064e\",\"largefrontend_flutter_chapter5_transform.md\":\"22708e1e\",\"largefrontend_flutter_chapter14_layer.md\":\"4ed449fc\",\"largefrontend_flutter_chapter11_websocket.md\":\"0a022e47\",\"largefrontend_flutter_chapter5_fittedbox.md\":\"db915726\",\"largefrontend_flutter_chapter2_index.md\":\"d8e937b7\",\"largefrontend_flutter_chapter3_radio_and_checkbox.md\":\"025aba21\",\"largefrontend_flutter_chapter13_locallization_implement.md\":\"dc4b83fe\",\"largefrontend_flutter_chapter2_thread_model_and_error_report.md\":\"a29199d6\",\"largefrontend_flutter_chapter5_decoratedbox.md\":\"20f093ba\",\"largefrontend_flutter_chapter14_render_object.md\":\"55a1b551\",\"largefrontend_flutter_chapter14_element_buildcontext.md\":\"4272eae4\",\"largefrontend_flutter_chapter4_wrap_and_flow.md\":\"ac41b5bd\",\"largefrontend_flutter_chapter11_file_operation.md\":\"996a3a5d\",\"largefrontend_flutter_chapter13_faq.md\":\"ba7b9c70\",\"largefrontend_webfrontend_md_10.md\":\"51e0a312\",\"largefrontend_webfrontend_md_24.md\":\"0a4abd08\",\"largefrontend_flutter_chapter14_flutter_app_startup.md\":\"9b5942a1\",\"largefrontend_flutter_chapter10_custom_paint.md\":\"8b2a0a46\",\"largefrontend_flutter_chapter2_flutter_assets_mgr.md\":\"0e38b701\",\"largefrontend_interview_01.md\":\"2ac4b56d\",\"largefrontend_flutter_chapter8_index.md\":\"e83d3d80\",\"largefrontend_webfrontend_md_16.md\":\"a5610416\",\"largefrontend_flutter_chapter9_index.md\":\"3fb342a7\",\"largefrontend_flutter_chapter8_listener.md\":\"1b9b0316\",\"largefrontend_webfrontend_md_37.md\":\"3cacf3a2\",\"largefrontend_flutter_chapter4_layoutbuilder.md\":\"3d4bf94b\",\"largefrontend_webfrontend_md_17.md\":\"ee74edfb\",\"largefrontend_webfrontend_md_13.md\":\"b180bb5b\",\"largefrontend_flutter_chapter7_index.md\":\"0eb456b2\",\"largefrontend_webfrontend_md_31.md\":\"309453c7\",\"largefrontend_webfrontend_md_25.md\":\"95fdb4fe\",\"largefrontend_webfrontend_md_12.md\":\"3b14e491\",\"largefrontend_webfrontend_md_23.md\":\"18f08d72\",\"largefrontend_flutter_chapter11_json_model.md\":\"c84d2ccf\",\"largefrontend_webfrontend_md_01.md\":\"fbb7a35f\",\"largefrontend_flutter_chapter3_progress.md\":\"9bc148d3\",\"largefrontend_webfrontend_md_18.md\":\"ee651cc6\",\"largefrontend_flutter_sponsor.md\":\"e71431a9\",\"largefrontend_webfrontend_md_36.md\":\"f22f6d0d\",\"largefrontend_webfrontend_md_07.md\":\"71c2ad1d\",\"largefrontend_webfrontend_md_19.md\":\"e7907728\",\"largefrontend_webfrontend_md_20.md\":\"5a68bb4f\",\"largefrontend_webfrontend_md_32.md\":\"91d2f39f\",\"largefrontend_flutter_chapter7_futurebuilder_and_streambuilder.md\":\"af3535c0\",\"largefrontend_flutter_chapter9_hero.md\":\"25b46cf2\",\"largefrontend_flutter_chapter8_notification.md\":\"909f6756\",\"largefrontend_webfrontend_md_08.md\":\"0d59dce4\",\"largefrontend_flutter_chapter3_img_and_icon.md\":\"8b409b55\",\"largefrontend_webfrontend_md_04.md\":\"d6c15d55\",\"largefrontend_flutter_chapter9_animated_switcher.md\":\"9c0b3119\",\"largefrontend_webfrontend_md_22.md\":\"7d6fea68\",\"largefrontend_flutter_chapter2_flutter_widget_intro.md\":\"13414fea\",\"largefrontend_flutter_chapter13_intl.md\":\"d4776610\",\"largefrontend_overview_index.md\":\"3c87f69c\",\"largefrontend_flutter_chapter5_container.md\":\"373cc694\",\"largefrontend_wechatminiprogram_index.md\":\"7f40708d\",\"largefrontend_webfrontend_md_38.md\":\"3b6bb332\",\"linux_index.md\":\"624ecc66\",\"largefrontend_webfrontend_md_35.md\":\"f0538423\",\"myteam_index.md\":\"91a2c11f\",\"largefrontend_flutter_chapter6_pageview.md\":\"36fcbc65\",\"largefrontend_webfrontend_md_03.md\":\"2e5087f0\",\"largefrontend_webfrontend_md_21.md\":\"c181316e\",\"largefrontend_flutter_chapter3_input_and_form.md\":\"e4fba9dd\",\"largefrontend_flutter_chapter14_compositing.md\":\"6d96f932\",\"largefrontend_webfrontend_md_29.md\":\"c6fada7d\",\"largefrontend_flutter_chapter4_constraints.md\":\"9e4baace\",\"largefrontend_webfrontend_md_11.md\":\"63cd9c49\",\"largefrontend_webfrontend_md_14.md\":\"1423aeb1\",\"largefrontend_flutter_chapter6_tabview.md\":\"56048fb1\",\"largefrontend_webfrontend_md_33.md\":\"fdbe86fa\",\"largefrontend_flutter_chapter6_nestedscrollview.md\":\"2a66b86a\",\"largefrontend_webfrontend_md_34.md\":\"ad8ea220\",\"largefrontend_flutter_chapter2_flutter_router.md\":\"bb6e776c\",\"largefrontend_webfrontend_md_30.md\":\"cd2cb5aa\",\"largefrontend_flutter_chapter4_flex.md\":\"ebd43d6a\",\"largefrontend_webfrontend_md_09.md\":\"eedafcb1\",\"largefrontend_webfrontend_md_40.md\":\"4d4fefa1\",\"largefrontend_flutter_join_us.md\":\"44c81d86\",\"largefrontend_flutter_chapter9_route_transition.md\":\"f56cf837\",\"largefrontend_uniapp_index.md\":\"fa387c39\",\"largefrontend_flutter_chapter4_row_and_column.md\":\"964229f5\",\"largefrontend_flutter_chapter9_stagger_animation.md\":\"315538f5\",\"largefrontend_flutter_chapter3_text.md\":\"6720230c\",\"largefrontend_flutter_chapter10_watermark.md\":\"2e4ddf03\",\"largefrontend_flutter_chapter11_http.md\":\"efd2ee64\",\"largefrontend_webfrontend_md_06.md\":\"e20b049a\",\"largefrontend_flutter_summary.md\":\"2e31b0a8\",\"largefrontend_flutter_chapter6_animatedlist.md\":\"20d9436f\",\"largefrontend_flutter_chapter5_clip.md\":\"91f4174c\",\"largefrontend_webfrontend_md_02.md\":\"40cd2099\",\"largefrontend_flutter_chapter6_gridview.md\":\"b3be7e60\",\"largefrontend_flutter_chapter6_custom_scrollview.md\":\"01be950c\",\"largefrontend_flutter_chapter11_download_with_chunks.md\":\"9379cc89\",\"largefrontend_flutter_chapter6_scroll_controller.md\":\"ffa2d5fd\",\"largefrontend_flutter_chapter9_intro.md\":\"3fc29b5f\",\"largefrontend_flutter_chapter1_dart.md\":\"6bba4e7c\",\"largefrontend_flutter_chapter7_inherited_widget.md\":\"5a21d704\",\"largefrontend_flutter_index.md\":\"cd81fbbe\",\"largefrontend_flutter_reference.md\":\"36336375\",\"largefrontend_flutter_chapter6_keepalive.md\":\"535137be\",\"largefrontend_flutter_chapter14_layout.md\":\"0d7a8df3\",\"largefrontend_flutter_chapter6_sliver.md\":\"0c8b7553\",\"largefrontend_webfrontend_md_39.md\":\"aef49a57\",\"largefrontend_webfrontend_md_15.md\":\"bf9ff1a1\",\"largefrontend_flutter_chapter14_image_and_cache.md\":\"9622c56b\",\"largefrontend_webfrontend_md_28.md\":\"83169d10\",\"largefrontend_flutter_chapter8_eventbus.md\":\"fc0db86d\",\"largefrontend_flutter_chapter7_value_listenable_builder.md\":\"a0c76121\",\"largefrontend_flutter_preface.md\":\"604b9934\",\"largefrontend_flutter_chapter7_theme.md\":\"c21036bc\",\"largefrontend_webfrontend_md_05.md\":\"f7607009\",\"largefrontend_flutter_chapter6_listview.md\":\"472910bd\",\"largefrontend_flutter_chapter7_provider.md\":\"ab590744\",\"largefrontend_flutter_chapter8_gesture.md\":\"859bbb38\",\"largefrontend_flutter_chapter9_animated_widgets.md\":\"e8cd3c4d\",\"largefrontend_webfrontend_md_26.md\":\"4928006b\",\"largefrontend_flutter_chapter10_gradient_circular_progress_demo.md\":\"b3d48a2c\",\"largefrontend_flutter_chapter8_hittest.md\":\"e963812b\",\"largefrontend_flutter_chapter7_willpopscope.md\":\"d224ecce\",\"largefrontend_flutter_chapter9_animation_structure.md\":\"672eb3c0\",\"largefrontend_flutter_chapter8_gesture_conflict.md\":\"15bbadba\",\"largefrontend_webfrontend_md_27.md\":\"0b5b5e39\",\"largefrontend_flutter_chapter7_dailog.md\":\"5f91e51a\",\"largefrontend_flutter_chapter10_custom_checkbox.md\":\"0db7d50f\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"zh-CN\",\"dir\":\"ltr\",\"title\":\"落光的Pro博客\",\"description\":\"Vite & Vue powered static site generator.\",\"base\":\"./\",\"head\":[],\"appearance\":true,\"themeConfig\":{\"search\":{\"provider\":\"local\",\"options\":{\"locales\":{\"zh\":{\"translations\":{\"button\":{\"buttonText\":\"搜索文档\",\"buttonAriaLabel\":\"搜索文档\"},\"modal\":{\"noResultsText\":\"无法找到相关结果\",\"resetButtonTitle\":\"清除查询条件\",\"footer\":{\"selectText\":\"选择\",\"navigateText\":\"切换\"}}}}}}},\"logo\":\"/icon-radius.png\",\"nav\":[{\"text\":\"大前端\",\"items\":[{\"text\":\"Web前端\",\"link\":\"/largeFrontEnd/webFrontEnd/md/01.md\"},{\"text\":\"Uni-app\",\"link\":\"/largeFrontEnd/uniapp/\"},{\"text\":\"微信小程序\",\"link\":\"/largeFrontEnd/weChatMiniProgram/\"},{\"text\":\"Electron\",\"link\":\"/largeFrontEnd/electron/\"},{\"text\":\"Flutter\",\"link\":\"/largeFrontEnd/flutter/\"},{\"text\":\"前端面试题\",\"link\":\"/largeFrontEnd/interview/01.md\"}]},{\"text\":\"后端\",\"link\":\"/backEnd/\"},{\"text\":\"Linux\",\"link\":\"/linux/\"},{\"text\":\"我的团队\",\"link\":\"/myTeam/\"}],\"sidebar\":{\"/largeFrontEnd/webFrontEnd/\":[{\"text\":\"第一阶段  基础入门\",\"collapsed\":true,\"items\":[{\"text\":\"一、基础认知\",\"link\":\"/largeFrontEnd/webFrontEnd/md/01.md\"},{\"text\":\"二、HTML标签学习\",\"link\":\"/largeFrontEnd/webFrontEnd/md/02.md\"},{\"text\":\"三、HTML基础\",\"link\":\"/largeFrontEnd/webFrontEnd/md/03.md\"},{\"text\":\"四、CSS基础\",\"link\":\"/largeFrontEnd/webFrontEnd/md/04.md\"},{\"text\":\"五、CSS进阶\",\"link\":\"/largeFrontEnd/webFrontEnd/md/05.md\"},{\"text\":\"六、盒子模型\",\"link\":\"/largeFrontEnd/webFrontEnd/md/06.md\"},{\"text\":\"七、CSS浮动\",\"link\":\"/largeFrontEnd/webFrontEnd/md/07.md\"},{\"text\":\"八、CSS定位装饰\",\"link\":\"/largeFrontEnd/webFrontEnd/md/08.md\"},{\"text\":\"九、CSS精灵图\",\"link\":\"/largeFrontEnd/webFrontEnd/md/09.md\"},{\"text\":\"项目前置认知\",\"link\":\"/largeFrontEnd/webFrontEnd/md/10.md\"},{\"text\":\"项目结构搭建\",\"link\":\"/largeFrontEnd/webFrontEnd/md/11.md\"},{\"text\":\"字体图标、动画\",\"link\":\"/largeFrontEnd/webFrontEnd/md/12.md\"},{\"text\":\"移动web开发\",\"link\":\"/largeFrontEnd/webFrontEnd/md/13.md\"}]},{\"text\":\"第二阶段 技术进阶\",\"collapsed\":true,\"items\":[{\"text\":\"JavaScript深入浅出\",\"link\":\"/largeFrontEnd/webFrontEnd/md/14.md\"},{\"text\":\"JavaScript核心之web APIs\",\"link\":\"/largeFrontEnd/webFrontEnd/md/15.md\"},{\"text\":\"一、Web APIS导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/16.md\"},{\"text\":\"二、DOM导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/17.md\"},{\"text\":\"三、事件高级导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/18.md\"},{\"text\":\"四、BOM导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/19.md\"},{\"text\":\"五、PC网页特效导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/20.md\"},{\"text\":\"六、移动端特效导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/21.md\"},{\"text\":\"七、本地存储导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/22.md\"},{\"text\":\"jQuery入门导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/23.md\"},{\"text\":\"数据可视化项目导读\",\"link\":\"/largeFrontEnd/webFrontEnd/md/24.md\"}]},{\"text\":\"Node.js\",\"collapsed\":true,\"items\":[{\"text\":\"buffer(缓冲器)\",\"link\":\"/largeFrontEnd/webFrontEnd/md/25.md\"},{\"text\":\"计算机基础\",\"link\":\"/largeFrontEnd/webFrontEnd/md/26.md\"},{\"text\":\"fs模块\",\"link\":\"/largeFrontEnd/webFrontEnd/md/27.md\"},{\"text\":\"path模块\",\"link\":\"/largeFrontEnd/webFrontEnd/md/28.md\"},{\"text\":\"HTTP模块\",\"link\":\"/largeFrontEnd/webFrontEnd/md/29.md\"},{\"text\":\"Express框架\",\"link\":\"/largeFrontEnd/webFrontEnd/md/30.md\"},{\"text\":\"Mongodb\",\"link\":\"/largeFrontEnd/webFrontEnd/md/31.md\"}]},{\"text\":\"Ajax\",\"link\":\"/largeFrontEnd/webFrontEnd/md/32.md\"},{\"text\":\"前端面试题\",\"collapsed\":true,\"items\":[{\"text\":\"一、CSS\",\"link\":\"/largeFrontEnd/webFrontEnd/md/33.md\"},{\"text\":\"二、JavaScript\",\"link\":\"/largeFrontEnd/webFrontEnd/md/34.md\"},{\"text\":\"三、HTML5CSS3\",\"link\":\"/largeFrontEnd/webFrontEnd/md/35.md\"},{\"text\":\"四、Vue\",\"link\":\"/largeFrontEnd/webFrontEnd/md/36.md\"},{\"text\":\"五、Echarts\",\"link\":\"/largeFrontEnd/webFrontEnd/md/37.md\"},{\"text\":\"六.Uni-APP\",\"link\":\"/largeFrontEnd/webFrontEnd/md/38.md\"},{\"text\":\"七、webpack\",\"link\":\"/largeFrontEnd/webFrontEnd/md/39.md\"},{\"text\":\"八、Git\",\"link\":\"/largeFrontEnd/webFrontEnd/md/40.md\"}]}],\"/largeFrontEnd/flutter/\":[{\"text\":\"Flutter入门到实战\",\"collapsed\":false,\"items\":[{\"text\":\"第一章 初识Flutter\",\"link\":\"/largeFrontEnd/flutter/chapter1/index.md\"},{\"text\":\"第二章 简介\",\"link\":\"/largeFrontEnd/flutter/chapter2/index.md\"},{\"text\":\"第三章 基础组件\",\"link\":\"/largeFrontEnd/flutter/chapter3/index.md\"},{\"text\":\"第四章 布局类组件\",\"link\":\"/largeFrontEnd/flutter/chapter4/index.md\"},{\"text\":\"第五章 容器类Widget\",\"link\":\"/largeFrontEnd/flutter/chapter5/index.md\"},{\"text\":\"第六章 可滚动组件\",\"link\":\"/largeFrontEnd/flutter/chapter6/index.md\"},{\"text\":\"第七章 功能型Widget简介\",\"link\":\"/largeFrontEnd/flutter/chapter7/index.md\"},{\"text\":\"第八章 事件处理与通知\",\"link\":\"/largeFrontEnd/flutter/chapter8/index.md\"},{\"text\":\"第九章 动画\",\"link\":\"/largeFrontEnd/flutter/chapter9/index.md\"},{\"text\":\"第十章 自定义组件\",\"link\":\"/largeFrontEnd/flutter/chapter10/index.md\"},{\"text\":\"第十一章 文件操作\",\"link\":\"/largeFrontEnd/flutter/chapter11/index.md\"},{\"text\":\"第十二章 Flutter 扩展\",\"link\":\"/largeFrontEnd/flutter/chapter12/index.md\"},{\"text\":\"第十三章 多语言\",\"link\":\"/largeFrontEnd/flutter/chapter13/index.md\"},{\"text\":\"第十四章 高级进阶\",\"link\":\"/largeFrontEnd/flutter/chapter14/index.md\"}]}]},\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://gitee.com/luoguangguang\"},{\"icon\":{\"svg\":\"<svg role=\\\"img\\\" viewBox=\\\"0 0 24 24\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\"><title>Dribbble</title><path d=\\\"M12...6.38z\\\"/></svg>\"},\"link\":\"...\",\"ariaLabel\":\"cool link\"}],\"footer\":{\"message\":\"Released under the MIT License.\",\"copyright\":\"Copyright © 2023-present LG of GZU\"},\"editLink\":{\"pattern\":\"https://gitee.com/luoguangguang/note/tree/master/docs/:path\",\"text\":\"在Gitee上参与编辑此页\"},\"carbonAds\":{\"code\":\"your-carbon-code\",\"placement\":\"your-carbon-placement\"}},\"locales\":{},\"scrollOffset\":90,\"cleanUrls\":false}");</script>
    
  </body>
</html>